Codeforces 633E. Startup Funding (概率+非递归线段树/Sparse Table +二分)


题意: 题目有一点点复杂。  有n(1 <= n <=10^6)周,第A周有V[A]和C[A]两个值,题目定义了区间[L,R]对应的值的计算p(L,R)=min(100*max(区间内的V),min(区间内的C))。

现在,对于每个L值,都有一个可以最大化p(L,R)值的R,假设当左端点为L时,最大的p(L,R)的值为Q[L]。

题目问,在这些L值中,随机选择k个作为L值,对于每个L值计算可能达到的最大p(L,R),然后求平均数,问这个结果的数学期望是多少。


简略思路:

第一步:求出上面定义的Q[L]数组

第二步:将Q数组进行排序,从小到大排序。

第三步:计算概率,利用下面的公式:


公式的解释,对于Q[t],在Q[t]右边的数有n-t个,假设Q[t]要作为k个数中的最小值,那么其余的k-1个数都必须在它右边,

所以在n-t个数中取k-1个数的每种方案中,都要计算一次Q[t],最后再除以总的方案数n中选k。


第一步:求Q数组

为了省事,直接让所有的V都乘上了100,所以公式变为:p(L,R)=min(max(区间内的V),min(区间内的C))。

容易看到,对于每个L,在R慢慢增加的情况下,p(L,R)先增后减(也有可能只增或只减),所以二分法可以求出最大p值。

但要注意,不能用p值的大小作为二分法的判断量,因为对于很多个R,p(L,R)的值是一样的,而当一样时,没法判断是在上升阶段还是在下降阶段。

所以二分法其实是在判断max(区间内V)和min(区间内C)这两个值哪个小,如果max(区间内V)小,那么处于上升趋势,如果min(区间内C)小,那么处于下降趋势。

用二分法可以找到上升趋势到下降趋势的分界线,我的代码中,用二分法找到上升部分的最后一个值,但是此时下降部分的第一个值仍然可能大于上升部分的最后一个值,

所以二分法求出上升部分的最后一个值之后,还需要再往前看一个数,看一下值是否更大。


二分法的判断中,需要求出max(区间内V)和min(区间内C)这两个值,于是需要一个数据结构来维护这个值。

方法有二: 

1.非递归线段树  (  43100KB  ,2.9秒)  

 2.Sparse Table (176100KB ,2.4秒)

补充:Sparse Table的查询中会用到log2这个函数,将log2进行预处理之后,查询就是真正的O(1)了,这种做法是:(180000KB , 1.5秒)。

题目时限是3秒。

非递归线段树O(n)建树,O(logn)查询,加上二分法的O(logn),所以计算Q值的复杂度是O(n(logn)^2) ,用时2.9秒。

Sparse Table O(nlogn)建表,用时2.4秒,预处理log2函数之后查询复杂度O(1),用时1.5秒。


Sparse Table花的空间是非递归线段树的5倍,不过快了很多。

Sparse Table是刚刚发现的很神奇的算法,空间复杂度O(nlogn),查询复杂度O(1),缺点就是不支持对数组的快速修改。

所以不需要修改的区间最值问题,就可以考虑使用Sparse Table来解决。


Sparse Table的思路(动态规划)以最大值为例:

建表:Max[ i ] [ j ] 表示区间 [ i , i + 2^j )的最值。

递推式:Max[ i ][ j ] = max(Max[ i ][ j - 1 ] , Max [i + (1<<(j-1)][ j ] )。

所以按照递推式,把所有可能用到的区间的Max表都建好。

这里说的是所有可能用到的区间,Max[ i ][ j ] 对应的区间是[ i , i + 2^j ),所以所有[ i , i + 2^j )在[1,n]以内的 i 和 j 的Max值都需要计算,而其余的都不需要计算。

代码中只计算了需要的部分,各种条件就是在保证对应的区间不出界(出界的后面一定不会用到,所以值不需要管)。

查询:

查询时,假设查询区间[s,t],那么先求k使得 (t-s+1)/2 < 2^k <= t-s+1   这时 t = floor(log2(t-s+1)),正的double转成int时自动取floor所以直接强制转换成int就可以了。

有了k之后,已经知道了从s开始2^k个数的最大值(即Max[s][k]),也知道了从t往前2^k个数的值(即Max[t - (1<<k)+1][k])。

取这两个值的最大值就是区间s到t的最大值,所以差不多可以算是O(1)查询。


代码如下:

//Sparse Table 
int Max[maxn][21];
int Min[maxn][21];
void BuildSparseTable(){
	//填好j=0的值 
	for(int i=1;i<=n;++i) Max[i][0]=V[i],Min[i][0]=C[i];
	//计算Sparse Table 
	for(int j=1;(1<<j) <= n;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
			Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
		}
	}
}
void Query(int L,int R,int &MaxV,int &MinC){
	int k = int(log2(R-L+1.0));
	MaxV=max(Max[L][k],Max[R-(1<<k)+1][k]);
	MinC=min(Min[L][k],Min[R-(1<<k)+1][k]);
}


第二步:对Q排序

不用多说,直接sort函数


第三步:计算答案


在计算中要注意不能每次都重新求C,那样会超时,注意到C每次只变了下标,所以可以利用组合数的递推关系,来递推每一个C,减少了很多计算量。


然后附上两份代码:

非递归线段树(43100KB,2.9秒):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
#include<iomanip>
#include <cstring>
#include <algorithm>
#define out(i) <<#i<<"="<<(i)<<"  "
#define OUT1(a1) cout out(a1) <<endl
#define OUT2(a1,a2) cout out(a1) out(a2) <<endl
#define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl
#define LD long double
#define maxn 1000007
using namespace std;
//非递归线段树 
int Max[maxn<<2],Min[maxn<<2],N;
void Init(int n){//初始化 
	N = 1; while(N < n + 2) N <<= 1;
	for(int i = 2*N;--i;) Max[i]=0,Min[i]=10000000;
}
void UpdateMax(int X,int V){//点更新
	for(int s = N + X;s;s >>= 1) 
		if(V > Max[s]) Max[s] = V;
		else break;
}
void UpdateMin(int X,int V){//点更新
	for(int s = N + X;s;s >>= 1) 
		if(V < Min[s]) Min[s] = V;
		else break;
}
void Query(int L,int R,int &MaxV,int &MinC){//获取区间最大值最小值 
	MaxV=0;MinC=10000000;
	for(int s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1){
		if(~s&1) MaxV=max(MaxV,Max[s^1]),MinC=min(MinC,Min[s^1]);
		if( t&1) MaxV=max(MaxV,Max[t^1]),MinC=min(MinC,Min[t^1]);
	}
}
int n,k,x;
int V[maxn];//题目数据V 
int C[maxn];//题目数据C 
int Q[maxn];//排序前:每个L对应的最优R值对应的答案
//二分查找 
void BinarySearch(int X){
	int L=X,R=n+1,M,MaxV,MinC;//[L,R) last Max <= Min
	while((L+1)^R){
		M = (L+R)>>1;
		Query(X,M,MaxV,MinC);
		if(MaxV <= MinC) L = M;
		else R = M;
	}
	Query(X,L,MaxV,MinC);
	Q[X]=min(MaxV,MinC);
	if(L < n) Q[X]=max(Q[X],min(max(MaxV,V[L+1]),min(MinC,C[L+1])));
}
int main(void)
{
	while(~scanf("%d%d",&n,&k)){
		Init(n);
		for(int i=1;i<=n;++i)	scanf("%d",&V[i]),UpdateMax(i,V[i]*=100);
		for(int i=1;i<=n;++i)   scanf("%d",&C[i]),UpdateMin(i,C[i]);
		//二分法计算从每个L开始的最优值
		for(int i=1;i<=n;++i) BinarySearch(i);
		//从小到大排序 
		sort(Q+1,Q+n+1);
		//计算结果 
		LD ANS=(LD)(0);
		LD CF=(LD)k/n;
		for(int t=1;t <= n-k+1;++t){
			ANS+=CF*Q[t];
			CF*=(LD)(n-k-t+1)/(n-t);
		}
		cout<<fixed<<setprecision(7)<<ANS<<endl;
	}
return 0;
}

Sparse Table代码(176100KB,2.4秒):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
#include<iomanip>
#include <cstring>
#include <algorithm>
#define out(i) <<#i<<"="<<(i)<<"  "
#define OUT1(a1) cout out(a1) <<endl
#define OUT2(a1,a2) cout out(a1) out(a2) <<endl
#define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl
#define LD long double
#define maxn 1000007
using namespace std;
int n,k,x;
int V[maxn];//题目数据V 
int C[maxn];//题目数据C 
int Q[maxn];//排序前:每个L对应的最优R值对应的答案

//Sparse Table 
int Max[maxn][21];
int Min[maxn][21];
void BuildSparseTable(){
	//填好j=0的值 
	for(int i=1;i<=n;++i) Max[i][0]=V[i],Min[i][0]=C[i];
	//计算Sparse Table 
	for(int j=1;(1<<j) <= n;++j){
		for(int i=1;i+(1<<j)-1<=n;++i){
			Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
			Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
		}
	}
}
void Query(int L,int R,int &MaxV,int &MinC){
	int k = int(log2(R-L+1.0));
	MaxV=max(Max[L][k],Max[R-(1<<k)+1][k]);
	MinC=min(Min[L][k],Min[R-(1<<k)+1][k]);
}
//二分查找 
void BinarySearch(int X){
	int L=X,R=n+1,M,MaxV,MinC;//[L,R) last Max <= Min
	while((L+1)^R){
		M = (L+R)>>1;
		Query(X,M,MaxV,MinC);
		if(MaxV <= MinC) L = M;
		else R = M;
	}
	Query(X,L,MaxV,MinC);
	Q[X]=min(MaxV,MinC);
	if(L < n) Q[X]=max(Q[X],min(max(MaxV,V[L+1]),min(MinC,C[L+1])));
}
int main(void)
{
	while(~scanf("%d%d",&n,&k)){
		for(int i=1;i<=n;++i)	scanf("%d",&V[i]),V[i]*=100;
		for(int i=1;i<=n;++i)   scanf("%d",&C[i]);
		BuildSparseTable();
		//二分法计算从每个L开始的最优值
		for(int i=1;i<=n;++i) BinarySearch(i);
		//从小到大排序 
		sort(Q+1,Q+n+1);
		//计算结果 
		LD ANS=(LD)(0);
		LD CF=(LD)k/n;
		for(int t=1;t <= n-k+1;++t){
			ANS+=CF*Q[t];
			CF*=(LD)(n-k-t+1)/(n-t);
		}
		cout<<fixed<<setprecision(7)<<ANS<<endl;
	}
return 0;
}


Sparse Table + 预处理log2函数  (180000KB , 1.5秒):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
#include<iomanip>
#include <cstring>
#include <algorithm>
#define out(i) <<#i<<"="<<(i)<<"  "
#define OUT1(a1) cout out(a1) <<endl
#define OUT2(a1,a2) cout out(a1) out(a2) <<endl
#define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl
#define LD long double
#define maxn 1000007
using namespace std;
int n,k,x;
int V[maxn];//题目数据V 
int C[maxn];//题目数据C 
int Q[maxn];//排序前:每个L对应的最优R值对应的答案

//Sparse Table 
int Max[maxn][21];
int Min[maxn][21];
int Log2[maxn];//Log2[x] = floor(log2(x))  预处理,为了加速 
void BuildSparseTable(){
	//填好j=0的值 
	for(int i=1;i<=n;++i) Max[i][0]=V[i],Min[i][0]=C[i];
	//计算Sparse Table 和Log2[] 
	int j,D,H;
	for(j=1,D=2,H=1;D <= n;++j,D <<= 1,H <<= 1){
		for(int i=1;i+ D - 1<=n;++i){
			Max[i][j]=max(Max[i][j-1],Max[i+H][j-1]);
			Min[i][j]=min(Min[i][j-1],Min[i+H][j-1]);
		}
		for(int i=H;i<D;++i) Log2[i]=j-1;
	}
	for(int i=H;i<=n;++i) Log2[i]=j-1;
}
void Query(int L,int R,int &MaxV,int &MinC){
	int k = Log2[R-L+1];
	MaxV=max(Max[L][k],Max[R-(1<<k)+1][k]);
	MinC=min(Min[L][k],Min[R-(1<<k)+1][k]);
}
//二分查找 
void BinarySearch(int X){
	int L=X,R=n+1,M,MaxV,MinC;//[L,R) last Max <= Min
	while((L+1)^R){
		M = (L+R)>>1;
		Query(X,M,MaxV,MinC);
		if(MaxV <= MinC) L = M;
		else R = M;
	}
	Query(X,L,MaxV,MinC);
	Q[X]=min(MaxV,MinC);
	if(L < n) Q[X]=max(Q[X],min(max(MaxV,V[L+1]),min(MinC,C[L+1])));
}
int main(void)
{
	while(~scanf("%d%d",&n,&k)){
		for(int i=1;i<=n;++i)	scanf("%d",&V[i]),V[i]*=100;
		for(int i=1;i<=n;++i)   scanf("%d",&C[i]);
		BuildSparseTable();
		//二分法计算从每个L开始的最优值
		for(int i=1;i<=n;++i) BinarySearch(i);
		//从小到大排序 
		sort(Q+1,Q+n+1);
		//计算结果 
		LD ANS=(LD)(0);
		LD CF=(LD)k/n;
		for(int t=1;t <= n-k+1;++t){
			ANS+=CF*Q[t];
			CF*=(LD)(n-k-t+1)/(n-t);
		}
		cout<<fixed<<setprecision(7)<<ANS<<endl;
	}
return 0;
}




猜你喜欢

转载自blog.csdn.net/u012891242/article/details/50825559
今日推荐