[机房测试]游戏

Description

给定长为 \(n\leq 10^5\) 的数列 \(\{t_n\}\),将其分为 \(K\leq 50\) 段,对每一段执行以下操作:

  1. 初始一个空盒子,对每个已经被抽中的数 \(i\),放 \(t_i\) 个到盒子里,对于最小的未被抽到的数 \(j\),放 \(t_j\) 个到盒子里。随机在盒子里抽一个数。

  2. 重复执行 \(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_]);
}
posted @ 2021-10-14 19:41  Kreap  阅读(20)  评论(0编辑  收藏  举报