• 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;
    }
    
  • 相关阅读:
    cakephp异常机制
    你的效率是整理出来的——张一驰
    eclipse常用总结
    cakephp写Lib要点
    代码整洁之道-马丁-第3章 函数
    PHP静态方法如何编写
    编程常用工具
    代码大全(第二版)
    PHP实现AES对称加密
    【pytest】(三) pytest运行多个文件
  • 原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1067D.html
Copyright © 2020-2023  润新知