一、题目
二、解法
不得不说 ( t construction force) 的 (dp) 题质量确实高,而且我敲出来调都没调,开心(sim)
首先看这道题就很好贪心,因为每个游戏可以多次打,所以一旦有激活机会后一定会一直打期望收益最大的那个关卡。记 (m=max b_icdot p_i),如果激活后还剩下 (i) 秒那么最大得分显然是 (icdot m)
注意对于这种求期望的最大值的题,策略是可能随着当前的局面改变的,最好用 (dp) 来解决。而本题的局面就只有是否激活关卡这一种,如果没有激活那么策略可能会根据剩余时间而不同,所以设 (dp[i]) 表示还剩 (i) 秒,现在未激活关卡的最大得分,转移需要枚举当前打的关卡:
考虑化成斜率优化的形式,设 (S_i=im-dp[i]),那么有:
这相当于用斜率为 (-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) 的求法,如果跳完之后还是当前转移点更优,那么就跳,最后再补跳一步即可。矩阵就这样写:
时间复杂度 (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]);
}