[机房测试]游戏
Description
给定长为 \(n\leq 10^5\) 的数列 \(\{t_n\}\),将其分为 \(K\leq 50\) 段,对每一段执行以下操作:
-
初始一个空盒子,对每个已经被抽中的数 \(i\),放 \(t_i\) 个到盒子里,对于最小的未被抽到的数 \(j\),放 \(t_j\) 个到盒子里。随机在盒子里抽一个数。
-
重复执行 \(1\) ,直到该段所有数被抽中。
最小化期望抽数的次数。
Solution
拍了半天以为稳了,结果暴力就写挂了,真的服了,但凡有个大样例……
考虑暴力 dp。令 \(dp_{i,j}\) 表示在将前 \(i\) 个数分为 \(j\) 段的最小期望次数。记 \(S\) 为 \(t\) 的前缀和,那么
\[dp_{i,j}=\min \{dp_{k,j-1}+\sum_{l=k+1}^i \frac{S_l-S_k}{t_l}\}
\]
可以得到一个 \(O(n^3k)\) 的做法。考虑分离变量,有
\[\sum_{l=k+1}^i \frac{S_l-S_k}{t_l}=\sum_{l=k+1}^i \frac{S_l}{t_l}-S_k\sum_{l=k+1}^i \frac{1}{t_l}
\]
记 \(b\) 为 \(\frac{S_l}{t_l}\) 的前缀和,\(c\) 为 \(\frac{1}{t_l}\) 的前缀和,就可以 \(O(1)\) 转移了。复杂度 \(O(n^2k)\)。
重写式子,有
\[dp_i=\min\{dp_k+b_i-b_k-S_kc_i+S_kc_k\}
\]
拆掉 \(\min\) 整理可得
\[dp_k-b_k+S_kc_k=c_iS_k+(dp_i-b_i)
\]
注意到 \(c\) 和 \(S\) 都是单增的,需要对纵截距取最小值,所以可以直接用单调队列维护一个下凸壳。
#include<stdio.h>
typedef double db;
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
const int N=2e5+7;
int q[N];
db X[N],Y[N],dp[N][51],b[N],c[N],S[N];
inline db calc(int a,int b){
return (Y[a]-Y[b])/(X[a]-X[b]);
}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
int n=read(),K_=read();
for(int i=1,x;i<=n;i++){
S[i]=S[i-1]+(x=read());
dp[i][1]=b[i]=S[i]/x+b[i-1];
c[i]=c[i-1]+(1.0/x);
}
for(int K=2;K<=K_;K++){
int hd=1,tl=0; q[++tl]=K-1;
Y[K-1]=dp[K-1][K-1]-b[K-1]+S[K-1]*c[K-1],X[K-1]=S[K-1];
for(int i=K;i<=n;i++){
while(hd<tl&&calc(q[hd],q[hd+1])<=c[i]) hd++;
dp[i][K]=Y[q[hd]]-c[i]*S[q[hd]]+b[i];
Y[i]=dp[i][K-1]-b[i]+S[i]*c[i],X[i]=S[i];
while(hd<tl&&calc(q[tl-1],q[tl])>=calc(q[tl],i)) tl--;
q[++tl]=i;
}
}
printf("%.2lf",dp[n][K_]);
}