CF1067D Computer Game

一、题目

点此看题

二、解法

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

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

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

\[dp[i]=\max_j dp[i-1]\cdot (1-p_j)+(i-1)m\cdot p_j+a_j\cdot p_j \]

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

\[dp[i]=\max_jdp[i-1]+p_j\cdot S_{i-1}+a_j\cdot p_j \]

这相当于用斜率为 \(-S_{i-1}\) 的直线去截取点 \((p_j,a_j\cdot 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\),因为 \(m\geq dp[i]-dp[i-1]\),显然时间增加带来的 \(dp\) 增量不会超过 \(m\)

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

\[\left[\begin{matrix}f_i&i&1\end{matrix}\right]\times \left[\begin{matrix}1-p_j&0&0\\m\cdot p_j&1&0\\a_j\cdot p_j&1&1\end{matrix}\right] =\left[\begin{matrix}f_{i+1}&i+1&1\end{matrix}\right] \]

时间复杂度 \(O(n\log 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\n",dp.a[1][1]);
}
posted @ 2021-07-24 16:05  C202044zxy  阅读(284)  评论(0编辑  收藏  举报