题目描述
\(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;
}
标签:UOJ918,le,int,题解,28,ge,maxn,蛋糕,res
From: https://www.cnblogs.com/peiwenjun/p/18554556