題目描述
\(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;
}