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

peiwenjun發表於2024-11-19

題目描述

\(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\) 單調不增。

分析

首先分析如何描述一個狀態,你已經獲得了前若干層蛋糕,然後你有一串尚未使用的 \(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;
}

相關文章