Codeforces 1067D

Codeforces 题面传送门 & 洛谷题面传送门

好题。

首先显然我们如果在某一次游戏中升级,那么在接下来的游戏中我们一定会一直打 (b_jp_j) 最大的游戏 (j),因为这样得到的期望收益最大。

因此我们设 (dp_i) 表示还剩 (i) 秒并且当前没有升级过的最大收益。

那么有 (dp_i=maxlimits_{j}{dp_{i-1}(1-p_j)+X(i-1)p_j+p_ja_j}),其中 (X=max{b_jp_j})

稍微解释一下上面的转移方程,我们枚举这一次选择玩哪个游戏,设为 (j),那么我们有 (p_j) 的概率获得胜利,之后每一轮期望会获得 (X) 的收益,得到的总收益就是 (X(i-1)),此外该轮还会获得 (a_j) 的收益,这种情况下的期望收益就是 (X(i-1)p_j+p_ja_j)。如果这次游戏没有取得胜利,那么问题转化为还剩 (i-1) 秒的情况,最大收益为 (dp_{i-1}),概率为 (1-p_j),期望收益为 ((1-p_j)dp_{i-1}),把两者加起来可以得到 (dp_{i-1}(1-p_j)+X(i-1)p_j+p_ja_j)。对所有 (j) 取个 (max) 即可得到上面的式子。

我们设 (S_i=X(i-1)-dp_{i-1}),那么上面的转移方程可以写成 (dp_i=max{dp_{i-1}+p_jS_i+p_ja_j})。注意到 (S_j) 是单调不减的,因为 (S_{j+1}-S_{j}=(iX-dp_i)-(X(i-1)-dp_{i-1})=X-(dp_i-dp_{i-1})),而 (dp_i-dp_{i-1}) 这个式子我们调用它的实际意义可知,它们的差距不可能大于一轮中的最大收益 (X),因此 (X-(dp_i-dp_{i-1})ge 0)

注意到上面改写过的 (dp) 方程可以视作,我们有若干个点 ((p_j,a_jp_j)),你要对于所有点,过其做斜率为 (-S_i),取最大截距作为 (dp_i) 相较于 (dp_{i-1}) 的增量,看到这样的设问我们可以很自然地想到斜率优化。具体来说我们建出这 (n) 个点的上凸壳,那么最优切点肯定在上凸壳上,又因为 (S_i) 单调递增,因此最优切点的横坐标肯定不断向右移,因此我们可以均摊 (mathcal O(n+T)) 地对于每一轮 DP 求出其最优切点。

如果这题 (T) 的数据范围比较小那么按照上文所述进行斜率优化即可通过,不过这丧心病狂的出题人偏偏将 (T) 数据范围加强到 (10^{10})。这就导致直接一轮轮推过去的做法无法通过,不过注意到对于上凸壳上每个点,它作为最优转移点存在的时刻是一段区间,因此我们考虑从左到右遍历上凸壳上每一个点,二分它作为最优转移点的区间的右端点 (t),矩阵快速幂算出 (t) 时刻的 DP 值,并通过判断 (S_t) 的最优切点是否是该点来判断应当向左还是向右二分。具体来说,对于一段连续的且最优转移点均为 (j) 的 DP,它们的 DP 转移均可写成以下形式:

[egin{bmatrix}dp_i&i&1end{bmatrix} imes egin{bmatrix} 1-p_j&0&0\ p_jX&1&0\ p_ja_j&1&1 end{bmatrix}= egin{bmatrix}dp_{i+1}&i+1&1end{bmatrix} ]

矩阵快速幂即可。这样复杂度 (nlog^2n),再加上矩阵快速幂的大常数,有亿点点危,不过如果我们对于所有 (kin[0,log_2(t)]) 预处理出矩阵的 (2^k) 幂,然后倍增最优转移点区间的右端点,那么时间复杂度可以做到 (nlog n)

const int MAXN=1e5;
const int LOG_T=36;
int sgn(ld x){return (x<-EPS)?-1:((x<EPS)?0:1);} 
struct mat{
	ld a[4][4];
	mat(){for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) a[i][j]=0;}
	mat operator *(const mat &rhs){
		mat res;
		for(int i=1;i<=3;i++) for(int j=1;j<=3;j++)
			for(int k=1;k<=3;k++) res.a[i][k]+=a[i][j]*rhs.a[j][k];
		return res;
	}
} cur,pw[LOG_T+2];
int n,a[MAXN+5],b[MAXN+5];
ll t;ld p[MAXN+5],X=0;
struct point{
	ld x,y;int id;
	point(ld _x=0,ld _y=0,int _id=0):x(_x),y(_y),id(_id){}
	bool operator <(const point &rhs) const{
		if(sgn(x-rhs.x)) return sgn(x-rhs.x)<0;
		return sgn(y-rhs.y)<0;
	}
} P[MAXN+5];
int stk[MAXN+5],tp=0;
void calc_hull(){
	sort(P+1,P+n+1);
	for(int i=1,j;i<=n;i=j+1){
		j=i;while(!sgn(P[i].x-P[j].x)) j++;j--;
		while(tp>1&&(P[stk[tp]].y-P[stk[tp-1]].y)*(P[j].x-P[stk[tp]].x)<
				    (P[stk[tp]].x-P[stk[tp-1]].x)*(P[j].y-P[stk[tp]].y)) --tp;
		stk[++tp]=j;
	}
}
ld calc(point p,ld x){return p.x*x+p.y;}
int main(){
	scanf("%d%lld",&n,&t);
	for(int i=1;i<=n;i++){
		scanf("%d%d%Lf",&a[i],&b[i],&p[i]);
		P[i]=point(p[i],a[i]*p[i],i);chkmax(X,b[i]*p[i]);
	}
	calc_hull();
	ll curp=0;cur.a[1][3]=1;
//	for(int i=1;i<=tp;i++) printf("(%.10Lf %.10Lf)
",P[stk[i]].x,P[stk[i]].y);
	for(int i=1;;){
		ld curS=X*curp-cur.a[1][1];
//		printf("%lld %.10Lf
",curp,curS);
		while(i<tp&&(P[stk[i+1]].y-P[stk[i]].y)>=-curS*(P[stk[i+1]].x-P[stk[i]].x)) i++;
		int id=P[stk[i]].id;
		pw[0].a[1][1]=1-p[id];pw[0].a[1][2]=0;pw[0].a[1][3]=0;
		pw[0].a[2][1]=p[id]*X;pw[0].a[2][2]=1;pw[0].a[2][3]=0;
		pw[0].a[3][1]=p[id]*a[id];pw[0].a[3][2]=1;pw[0].a[3][3]=1;
		for(int j=1;j<=LOG_T;j++) pw[j]=pw[j-1]*pw[j-1];
		for(int j=LOG_T;~j;j--) if(curp+(1ll<<j)<t){
			mat nw_mat=cur*pw[j];
			ld nw=X*nw_mat.a[1][2]-nw_mat.a[1][1];
			if((i==tp)||calc(P[stk[i]],nw)>calc(P[stk[i+1]],nw))
				curp+=(1ll<<j),cur=cur*pw[j];
		} cur=cur*pw[0];curp++;if(curp==t) break;
	} printf("%.10Lf
",cur.a[1][1]);
	return 0;
}
原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1067D.html