BS-2247 选钻石 题解

题意

有一个长度为 \(n\) 的随机数列 \(\{p_i\}\)。每个 \(p_i\) 独立等概率在 \(1\sim m\) 中选取。

你选数的过程如下:

  • 从 \(i=1\) 开始,每一步只能看到当前 \(p_i\) 。你需要决策是否选择 \(p_i\):若选择,则直接结束;否则继续看 \(p_{i+1}\)。
  • 不能回头选择 \(i\) 之前的数。

最大化选出的数的期望,求出这个最大期望。

\(1\le n\le 10^{9},2\le m\le 10^6\)。

Solution

由于每个数独立,\(i\) 之前的数对 \(i\) 处决策没有影响。不难得到一个 \(O(n)\) 的 DP:

设 \(f_i\) 为有 \(i\) 个数的最大期望,答案即为 \(f_n\)。

显然 \(f_1=(1+m)/2\)。对于一个位置 \(i\):

  • 若 \(p_i\le f_{i+1}\)(即 \(p_i\le \lfloor f_{i+1}\rfloor\))那么不选 \(p_i\)
  • 否则选 \(p_i\) 并退出

综上,设 \(t=\lfloor f_{i-1} \rfloor\),转移方程为:

\f_i=\\frac{1}{m}\\left(tf_{i-1}+\\frac{(m-t)(m+t+1)}{2}\\right) \\

注意到 \(f\) 满足:

  • \(f_i\) 单调递增
  • \(f_i<m\)

因此 \(\lfloor f_{i} \rfloor\) 单调不降,当 \(\lfloor f_{i} \rfloor=m-1\) 后不再改变。

我们先暴力 DP 直到 \(f_{i}\ge m-1\),设 \(a\) 为第一个满足此条件的 \(i\)。

此后转移方程变成:

\f_i=\\frac{m-1}{m}f_{i-1}+1\\quad (i\>a) \\

这是一个一阶线性递推,不动点法得:

\f_i-m=\\frac{m-1}{m}\\left(f_{i-1}-m\\right)\\quad (i\>a) \\

因此 \(f_n=(f_a-m)\left(\frac{m-1}{m}\right)^{n-a}+m\),快速幂解决即可。

时间复杂度

我们可以证明,这个算法的时间复杂度是 \(O(m\log m)\)。

引理

\\\forall x\\in (0,+\\infty),\\quad \\ln(1+x)\\ge \\frac{x}{1+x} \\


首先考虑一个不优的策略:看到 \(m\) 就拿,否则一直跳过。这个策略在 \(k\) 个数时的期望为:

\m\\left(1-\\left(\\frac{m-1}{m}\\right)\^k\\right) \\

令

\m\\left(1-\\left(\\frac{m-1}{m}\\right)\^k\\right)\\ge m-1 \\

得

\\\begin{aligned} m-m\\left(\\frac{m-1}{m}\\right)\^k\&\\ge m-1 \\\\ \\left(\\frac{m-1}{m}\\right)\^k\&\\le \\frac{1}{m} \\\\ \\left(\\frac{m}{m-1}\\right)\^k\&\\ge m \\\\ k\&\\ge\\log_{\\frac{m}{m-1}}m \\\\ k\&\\ge\\frac{\\ln m}{\\ln \\left(1+\\frac{1}{m-1}\\right)} \\\\ \\end{aligned} \\

又因为策略不优,有

\\\forall k\\ge \\frac{\\ln m}{\\ln \\left(1+\\frac{1}{m-1}\\right)},\\quad m-1\\le m\\left(1-\\left(\\frac{m-1}{m}\\right)\^k\\right)\\le f_k \\

因此

\a\\le \\frac{\\ln m}{\\ln \\left(1+\\frac{1}{m-1}\\right)} \\

令引理中 \(x=\frac{1}{m-1}\),得

\\\begin{aligned} \\ln \\left(1+\\frac{1}{m-1}\\right) \&\\ge \\frac{1}{m} \\\\ \\frac{\\ln m}{\\ln \\left(1+\\frac{1}{m-1}\\right)}\&\\le m\\ln m \\\\ a\& \\le m \\ln m \\end{aligned} \\

于是 DP 部分一定不会算超过 \(m\ln m\) 个,算法时间复杂度为 \(O(m\log m)\)。

Code

cpp 复制代码
#include <bits/stdc++.h>
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define db double
using namespace std;
db ksm(db x,int y){
    db res=1;
    while(y){
        if(y&1) res*=x;
        x*=x,y>>=1;
    }
    return res;
}
signed main(){
    int n,m,i=1,t;
    cin>>m>>n;
    cout<<fixed<<setprecision(3);
    db f=(m+1)/2.0;
    if(n==1) return cout<<f,0;
    while((t=floor(f))<m-1){
        f=(f*t+1.0*(m+t+1)*(m-t)/2)/m,++i;
        if(i==n) return cout<<f,0;
    }
    cout<<(f-m)*ksm(1.0*(m-1)/m,n-i)+m;
    return 0;
}