【题解】P3600 随机数生成器(概率,期望)


【题解】 P3600 随机数生成器

期望好题!

这道题从 11 号下午开始在机房写的,从十一号晚上调到十三号凌晨,终于调过去了!

第四道黑!(激动)

这道题让我自己是无论如何都想不出来的。这里完整详细叙述一遍思路,帮我自己梳理一遍。


题目链接

P3600 随机数生成器

思路分析

从问题入手,问题是求这 \(q\) 个区间中每个区间最小值的最大值的期望。

根据期望的定义,那么有:

\[\frac {\sum \limits_{i=1}^x f_i\times i}{x^n} \]

其中 \(f_i\) 表示的是区间最大值恰好为 \(i\) 的方案数。

考虑对于每个区间来说,显然若这个区间 \(A\) 包含另一个区间 \(B\),那么这个区间 \(A\) 的最小值一定小于等于区间 \(B\)

由于我们要求的是区间最小值的最大值,那么可以看出,区间 \(A\) 对最后答案无影响。

那么我们就可以把这些区间 \(A\) 删掉。可以发现,如果把剩下的区间按左端点从小到大排序,那么右端点一定是严格单调递增的。

回到上面那个式子。

显然 \(x^n\) 可以直接快速幂求得,那么问题就转化为如何求 \(f_i\)

\(f_i\) 并不好求,根据常用套路,那么我们可以考虑将恰好转化为最多

定义 \(g_i\) 表示的是区间最大值 \(\le i\) 的方案数。

那么对于每一个 \(i\),利用类似前缀和差分的思想,有:

\[f_i=g_i-g_{i-1} \]

即:区间最大值恰好为 \(i\) 的方案数是区间最大值 \(\le i\) 的方案数减去区间最大值 \(\le i-1\) 的方案数。

考虑如何求 \(g_i\)

当区间最大值整体 \(\le i\),那么就说明:每个区间都至少有一个 \(\le i\) 的数。

那么有:

\[g_i=\sum \limits_{j=1}^n tp_j \times i^j \times (x-i)^{n-j} \]

\(tp_j\) 表示的是在 \(1-n\) 个区间里的所有数中,选出 \(j\)\(\le i\) 的点,每个区间至少包含一个点的方案数。

我们要计算 \(g_i\),可以枚举 \(j\),那么对于这 \(j\)\(\le i\) 的点,有 \(i^j\) 种情况;对于剩下 \(> i\)\(n-j\) 个点,有 \((x-i)^{n-j}\) 种情况;那么根据乘法原理,每个 \(j\)\(g_i\) 的贡献就是:

\[tp_j \times i^j \times (x-i)^{n-j} \]

最后累加求和即可。

现在问题转化成如何求 \(tp_i\)

考虑 DP。

定义 \(dp_{i,j}\) 表示的是前 \(i\) 个位置放了 \(j\) 个点,且第 \(i\) 个位置必须放点,覆盖所有的左端点 \(\le i\) 的区间的方案数。

我们定义 \(L_i\)\(R_i\) 分别表示覆盖位置 \(i\)最左最右区间编号。

特殊地,对于没有被任何区间覆盖的位置 \(i\)\(R_i\) 定义为右端点严格小于 \(i\) 的最后一个区间,\(L_i=R_i+1\)

那么有:

\[dp_{i,j}=\sum \limits_{R_k+1 \ge L_i}dp_{k,j} \]

枚举 \(i,j,k\),时间复杂度 \(O(n^3)\)

考虑优化。可以发现参与转移的 \(k\) 是一段连续的区间,那么前缀和优化即可。时间复杂度:\(O(n^2)\)

即:

\[dp_{i,j}=sum_{i-1,j-1}-sum_{k-1,j-1} \]

其中 \(sum_{i,j}\) 表示的是上一层的 \(\sum \limits_{k=1}^i dp_{k,j}\)

这里要注意的一点是:\(k-1\) 有可能为负,所以要特判 \(k=0\) 的情况。(我因为这个问题挂了一下午,而且本地测试挂了点还能过就很离谱

那么 \(g_j\) 就可以计算出:

\[g_j=\sum \limits_{R_i=q}dp_{i,j} \]

总的时间复杂度:\(O(n^2)\)

最后我们来梳理一下求解过程:

求期望 \(\rightarrow\) 求区间最大值恰好\(i\) 方案数 \(f_i \rightarrow\) 求区间最大值 \(\le i\) 的方案数 \(g_i \rightarrow\) 求每个区间都至少有一个 \(\le i\) 的点的方案数 \(tp_i \rightarrow\)\(dp_{i,j}\)

求解步骤

  1. 区间去重+排序;

  2. 求出每个点 \(i\)\(R_i,L_i\)

  3. 枚举 \(i,j\),前缀和优化,计算出 \(dp_{i,j}\)

  4. 枚举 \(j\),根据 \(dp_{i,j}\),计算出 \(tp_j\)

  5. 枚举 \(i\),根据 \(tp_j\) 计算 \(g_i\)

  6. 根据 \(g_i\) 计算出 \(f_i\)

  7. 套用公式 \(\frac {\sum \limits_{i=1}^x f_i\times i}{x^n}\) 计算出最终答案。

实现代码

//luoguP3600
#include
#include
#include
#include
#define int long long
using namespace std;
const int mod=666623333;
const int maxn=2005;

struct Node{int l,r;}a[maxn],b[maxn];
int n,x,q;
int cnt,mx,ans,mi;
int vis[maxn],book[maxn],L[maxn],R[maxn]; 
//L[i] 和 R[i] 分别表示 i 可以覆盖到的编号最小和最大的区间 
int dp[maxn][maxn],tp[maxn],g[maxn],f[maxn],sum[maxn][maxn];
//dp[i][j] 表示前 i 个位置放了 j 个√,且第 i 个位置必须放√,覆盖所有的左端点 <=i 的区间的方案书。 
//tp[i] 表示的是在 [1,n] 内选出 i 个点,每个区间至少包含一个点的方案数。
//g[i] 表示的是区间最大值 <=i 时的方案数。
//f[i] 表示区间最大值恰好 =i 时的方案数。 

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}

int cmp(Node a,Node b){if(a.l==b.l)return a.r=a[j].l&&a[i].r<=a[j].r)vis[j]++;
            else if(a[i].l<=a[j].l&&a[i].r>=a[j].r)vis[i]++;
        }
    }
    for(int i=1;i<=q;i++)
    {
        if(!vis[i])b[++cnt].l=a[i].l,b[cnt].r=a[i].r;
    }
    return ;
}

void work()//求出 L[i] 和 R[i]
{
    //cout<=i)
            {
                if(!L[i])L[i]=j;
                R[i]=j;
            }
            else if(b[j].l>i)
            {
                if(!L[i])R[i]=j-1,L[i]=R[i]+1;
                //若 i 不被任何区间所覆盖,则 R[i] 设为最后一个右端点比它小的区间编号,L[i]=R[i]+1; 
                break;
            }
        }
        if(!L[i])R[i]=R[i-1],L[i]=R[i]+1;
    }
} 

int qpow(int a,int T)
{
    int ret=1;
    while(T)
    {
        if(T&1)(ret*=a)%=mod;
        (a*=a)%=mod;T>>=1;
    }
    return ret;
}

void out()
{
    for(int i=1;i<=x;i++)cout<