CF1067D Computer Game

一、题目

点此看题

二、解法

不得不说 ( t construction force)(dp) 题质量确实高,而且我敲出来调都没调,开心(sim)

首先看这道题就很好贪心,因为每个游戏可以多次打,所以一旦有激活机会后一定会一直打期望收益最大的那个关卡。记 (m=max b_icdot p_i),如果激活后还剩下 (i) 秒那么最大得分显然是 (icdot m)

注意对于这种求期望的最大值的题,策略是可能随着当前的局面改变的,最好用 (dp) 来解决。而本题的局面就只有是否激活关卡这一种,如果没有激活那么策略可能会根据剩余时间而不同,所以设 (dp[i]) 表示还剩 (i) 秒,现在未激活关卡的最大得分,转移需要枚举当前打的关卡:

[dp[i]=max_j dp[i-1]cdot (1-p_j)+(i-1)mcdot p_j+a_jcdot p_j ]

考虑化成斜率优化的形式,设 (S_i=im-dp[i]),那么有:

[dp[i]=max_jdp[i-1]+p_jcdot S_{i-1}+a_jcdot p_j ]

这相当于用斜率为 (-S_{i-1}) 的直线去截取点 ((p_j,a_jcdot p_j)) 得到最大截距,显然需要求出关卡的上凸包。常规做法是每次转移在凸包上二分,但是本题因为转移次数过多所以做不动,这时候不妨来猜一猜结论。

其实这个结论我自己猜出来了(只可惜我没想到凸包),贪心的来看我们取最大的 (p_j) 来获取激活机会是好的,但是因为在较小的时间下激活关卡的得分会被冲淡,所以此时我们就要把 (a_j) 也考虑进去,但是随着时间的增大转移点的 (p) 只会不减。

重新表述一遍这个结论:随着 (i) 的增大凸包上转移点的 (x) 坐标单增。证明可以考虑去切凸包直线斜率 (-S_i) 单减,也就是 (S_i) 单增,考虑 (S_i-S_{i-1}=m-dp[i]+dp[i-1]geq 0),因为 (mgeq dp[i]-dp[i-1]),显然时间增加带来的 (dp) 增量不会超过 (m)

那么考虑每个转移点转移了多少次即可,因为 (t) 很大所以可以考虑矩阵加速。我们倍增地跳,类似于 ( t lca) 的求法,如果跳完之后还是当前转移点更优,那么就跳,最后再补跳一步即可。矩阵就这样写:

[left[egin{matrix}f_i&i&1end{matrix} ight] imes left[egin{matrix}1-p_j&0&0\mcdot p_j&1&0\a_jcdot p_j&1&1end{matrix} ight] =left[egin{matrix}f_{i+1}&i+1&1end{matrix} ight] ]

时间复杂度 (O(nlog t)),本题精度要求超级高,求凸包的时候注意 (x) 相同时要按 (y) 排序。

三、总结

本题真是一个极不标准的斜率优化,但只要注意斜率优化的特征还是化得出来的:把 (j)(转移点)有关的项分离,把 (i) 有关的项分离,把和 (i,j) 都有关的项分离就可以看出是否可以斜率优化了,一定要主动地尝试斜率优化。

找结论其实也是有方法的,比如本题需要转移点的结论,那么我就去思考什么时候这个点才会来转移,可能结论就藏在其中。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define db double
const int M = 100005;
#define eps 1e-14
//精度要求真的高 
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,r,t,h[M],rk[M];db m,p[M],g[M];
struct mat
{
	db a[4][4];
	mat() {memset(a,0,sizeof a);}
	mat operator * (const mat &b) const
	{
		mat r;
		for(int i=1;i<=3;i++)
			for(int j=1;j<=3;j++)
				for(int k=1;k<=3;k++)
					r.a[i][k]+=a[i][j]*b.a[j][k];
		return r;
	}
}dp,A[35];
db Abs(db x) {return x>0?x:-x;}
db get(db x,int i) {return p[i]*x+g[i];}
bool cmp(int a,int b)
{
	if(Abs(p[a]-p[b])<eps) return g[a]>g[b];
	return p[a]<p[b];
}
mat con(int x)
{
	mat r;
	r.a[2][2]=r.a[3][2]=r.a[3][3]=1;
	r.a[1][1]=1-p[x];r.a[2][1]=p[x]*m;r.a[3][1]=g[x];
	return r;
}
signed main()
{
	n=read();t=read();
	for(int i=1;i<=n;i++)
	{
		db a,b;rk[i]=i;
		scanf("%lf %lf %lf",&a,&b,&p[i]);
		m=max(m,b*p[i]);g[i]=a*p[i];
	}
	sort(rk+1,rk+1+n,cmp);
	//find the convex hull
	for(int j=1;j<=n;j++)
	{
		int i=rk[j];
		while(k>1 && (g[i]-g[h[k]])*(p[h[k]]-p[h[k-1]])
		>=(g[h[k]]-g[h[k-1]])*(p[i]-p[h[k]])) k--;
		h[++k]=i;
	}
	dp.a[1][3]=1;db sp=0;
	for(int f=1,now=0;f<=k;)
	{
		while(f<=k && get(sp,h[f])<=get(sp,h[f+1])) f++;
		A[0]=con(h[f]);
		for(int i=1;i<=34;i++) A[i]=A[i-1]*A[i-1];
		for(int i=34;i>=0;i--)
		{
			if(now+(1ll<<i)>=t) continue;
			mat tmp=dp;tmp=tmp*A[i];
			db cur=tmp.a[1][2]*m-tmp.a[1][1];
			if(f==k || get(cur,h[f])>=get(cur,h[f+1]))
			{
				now+=(1ll<<i);
				dp=tmp;sp=cur;
			}
		}
		dp=dp*A[0];now++;
		sp=now*m-dp.a[1][1];
		if(now==t) break;
	}
	printf("%.8f
",dp.a[1][1]);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15055437.html