UOJ918 【UR #28】偷吃蛋糕 题解

题目描述

\(n\) 层蛋糕,第 \(i\) 层大小 \(c_i\) ,保证 \(c_i\) 单调不增。

初始你有第 \(1\) 层蛋糕,然后重复以下操作,直至没有蛋糕:

  • 吃掉最大的一层蛋糕,记其大小为 \(x\)
  • 如果还有至少 \(x\) 层蛋糕没有给你,主办方会按编号升序给你接下来的 \(x\) 层蛋糕。
  • 如果只有 \(y\) 层蛋糕(\(y\lt x\)),主办方会给你全部蛋糕,同时可以获得 \(x-y\) 的收益。
  • 每次主办方给你蛋糕时(假设要给你 \(x\) 层),老鼠会从这 \(x\) 层蛋糕等概率随机偷走一层蛋糕,你可以获得剩余 \(x-1\) 层蛋糕。

求收益的期望值对 \(998244353\) 取模的结果。

数据范围

  • \(1\le n\le 2000\)
  • \(1\le c_i\le n\)\(c_i\) 单调不增。

时间限制 \(\texttt{0.6s}\) ,空间限制 \(\texttt{512MB}\)

分析

首先分析如何描述一个状态,你已经获得了前若干层蛋糕,然后你有一串尚未使用的 \(c_i\) 序列。每次用掉序列开头\(c_i\) ,然后在序列末尾加入 \(c_i-1\) 个数。

因此你要有一点直觉,这题不是正经的动态规划,因为状态实在过于复杂。

接下来是人类智慧搜索剪枝。

由于我们只会在末尾添加 \(c_i\) ,因此接下来的 \(|\text{seq}|\) 步操作仅由序列中已知的 \(c_i\) 确定。如果仅通过这些 \(c_i\) 就可以确保让主办方给你全部蛋糕,我们可以在 \(O(n)\) 的时间内计算贡献。

具体的,如果可以确保主办方给你全部蛋糕(有做不到的情况,参见样例一),那么你的收益为你获得\(c_i\) 之和减去 \(n-1\)

假如 \(S=\sum\limits_{\text{used}}c_i+\sum\limits_{\text{in seq}}c_i\ge n\) ,扫描序列中的每个 \(c_i\) (模拟接下来的每一步操作——给你\(l\sim r\) 层蛋糕),这一步期望贡献 \(\frac{r-l}{r-l+1}\sum_{j=l}^rc_j\)


如果 \(c_i\) 很大,直觉告诉你 \(S\ge n\) 很快就会成立。

那到底快到什么程度呢?

\(m=\lceil\sqrt n\rceil\) ,如果 \(c_{c_1+1}\ge m\) ,那么 \(c_1\ge\cdots\ge c_{c_1+1}\ge m\) ,第一步操作后 \(S\gt c_1\cdot c_{c_1+1}\ge m^2\ge n\) ,一步就结束了。

因此如果一步没有结束,那么从第二步起在序列中添加的数都满足 \(c_i\lt m\le 45\) ,换言之 \(c_i\) 非常稠密。

于是容易想到另外一个强有力的剪枝。老鼠偷走相同的 \(c_i\) 本质上没有区别,我们只需要搜索一次,然后乘上相应概率。

这样一个状态的子状态个数为区间(这一步给你的蛋糕)中不同 \(c_i\) 的个数。定义势能 \(\varphi\) 为尚未给你\(c_i\) 最大值,假设当前状态有 \(x\) 个子状态,那么这些子状态的势能都至少减少 \(x-1\)

最坏情况下每次 \(x=2\) ,则 \(\varphi(n)=2^n\) 。理论状态上限为 \(n\cdot\varphi(\sqrt n)\) ,但实际根本卡不满,而且跑得飞快。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005,mod=998244353;
int n;
int c[maxn],s[maxn],inv[maxn],fir[maxn],lst[maxn];
bool vis[maxn];///记录每层蛋糕是否被偷走
int qpow(int a,int k)
{
    int res=1;
    for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
    return res;
}
int dfs(int d,int s1,int s2)
{///正在吃第 d 层蛋糕,已经用过的 c_i 之和为 s1 ,已经用过 & 序列中的 c_i 之和为 s2
    if(s2+1>=n)
    {
        int res=s2-(n-1);
        for(int l=s1+1+1,r=0;l<=n;l=r+1,d++)
        {
            while(d<=n&&vis[d]) d++;
            assert(d!=n+1),r=min(l+c[d]-1,n);
            res=(res+(mod+1ll-inv[r-l+1])*(s[r]-s[l-1]))%mod;
        }
        return res;
    }
    while(d<=n&&vis[d]) d++;
    if(d==n+1) return 0;
    int l=s1+1+1,r=s1+1+c[d],res=0,sum=s[r]-s[l-1];///主办方给你第 [l,r] 层
    assert(r<=n);
    for(int j=c[l];j>=c[r];j--)
    {///尝试偷吃 c_i=j 的蛋糕
        if(!lst[j]) continue;
        vis[max(l,fir[j])]=1;
        res=(res+(min(r,lst[j])-max(l,fir[j])+1ll)*dfs(d+1,s1+c[d],s2+sum-j))%mod;
        vis[max(l,fir[j])]=0;
    }
    return 1ll*res*inv[c[d]]%mod;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&c[i]),s[i]=s[i-1]+c[i],inv[i]=qpow(i,mod-2),lst[c[i]]=i;
        if(!fir[c[i]]) fir[c[i]]=i;
    }
    printf("%d\n",dfs(1,0,c[1]));
    return 0;
}
posted @ 2024-11-19 11:52  peiwenjun  阅读(7)  评论(0编辑  收藏  举报