[Luogu P6059]纯粹容器 senpai
题目链接:Luogu P6059
我傻了,比赛时写错了一个小细节,一看\(n\le 50\)还以为想假了
先把最大的\(a_i\)特判掉,答案显然为\(n-1\)。
我们枚举每一个\(a_i\)分别计算答案,找到左右距\(a_i\)最近且大于\(a_i\)的\(a_l,a_r\),那么\(a_i\)被击倒只有\([l,i]\)或者\([i,r]\)间的容器全部进行决斗。
(若\(a_l\)和\(a_r\)在此之前和其他容器决斗只可能不变或变得更大)
枚举\(a_i\)在第\(j\)轮被淘汰,设概率为\(p_{i,j}\),则\(a_i\)的期望存活轮数为\(\sum_{1\le j<n}\limits p_{i,j}*(j-1)\)
考虑怎么求\(p_{i,j}\),设\(g_{i,j}\)表示\(a_i\)在前\(j\)轮的某一次被淘汰的概率,则\(p_{i,j}=g_{i,j}-g_{i,j-1}\)
因为一共进行了\(n-1\)次决斗,设\(Calc(i,j)\)表示在这\(n-1\)轮决斗中选出\(i\)轮决斗全部在前\(j\)轮的概率
那么对于\(g_{i,j}\),要么被左边淘汰,要么被右边淘汰,容斥一下则
\(g_{i,j}=Calc(i-l,j)+Calc(r-i,j)-Calc(r-l,j)\)
(被左边淘汰的概率+被右边淘汰的概率-去掉两边决斗全部在前\(j\)场的重复计算)
当然如果某一边没有比\(a_i\)大的容器那答案就是\(Calc(i-l,j)\)或者\(Calc(r-i,j)\)
考虑计算\(Calc(i,j)\)。\(i\)场在前\(j\)场的方案数:\(A_j^i=\frac{j!}{(j-i)!}\),其他决斗的方案数:\((n-1-i)!\),总方案数:\((n-1)!\)
则\(Calc(i,j)=\frac{j!(n-1-i)!}{(j-i)!(n-1)!}\)
预处理阶乘及逆元,暴力计算即可做到\(O(n^2)\),不知道能不能继续优化,反正O(n^2)足够了
时间复杂度 \(O(n^2)\)
空间复杂度 \(O(n)\)
代码:
#include <cstdio>
typedef long long ll;
const int N=55,P=998244353;
int n,a[N],Fac[N],Inv[N],Ans[N];
ll Pow(ll a,int b,ll s=1){for(;b;b>>=1,a=a*a%P)if(b&1)s=s*a%P;return s;}
inline int Calc(int x,int l)//x场全部在前l场的概率
{
if(x>l)return 0;
return (ll)Fac[l]*Inv[l-x]%P*Fac[n-1-x]%P*Inv[n-1]%P;
}
int main()
{
scanf("%d",&n);
for(int i=Fac[0]=1;i<=n;++i)Fac[i]=(ll)Fac[i-1]*i%P;
Inv[n]=Pow(Fac[n],P-2);
for(int i=n;i>=1;--i)Inv[i-1]=(ll)Inv[i]*i%P;
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
{
int l=i-1,r=i+1;
while(l>=1&&a[l]<a[i])--l;
while(r<=n&&a[r]<a[i])++r;
if(l<1&&r>n){Ans[i]=n-1;continue;}
int Res=0,Pp=0;
for(int j=1;j<n;++j)
{
int Np=0;
if(l>=1)Np=Calc(i-l,j);
if(r<=n)Np=(Np+Calc(r-i,j))%P;
if(l>=1&&r<=n)Np=(Np-Calc(r-l,j)+P)%P;//容斥
Res=(Res+ll(j-1)*(Np-Pp+P))%P,Pp=Np;//这里没有p和g数组,Np代表g_{i,j},Pp代表g_{i,j-1}
}
Ans[i]=Res;
}
for(int i=1;i<=n;++i)printf("%d%c",Ans[i],i==n?'\n':' ');
return 0;
}