题目分析
算法0
每个管理员选哪一列,将构成一个长度为N−1N-1N−1的序列,序列的种数可以通过经典的将aaa个相同元素插入到一个没有该元素的长度为bbb的序列里问题,轻松求出。
若一列iii要求有空笼,则标号iii只出现ai−1a_i-1ai−1次,每种序列出现的概率都是一样的,一下子就算出来啦!
期望得分:0
算法1
分析一下算法0错在哪——每种序列的出现概率并不是相等的,因为每一个管理员选择列的时候,由于剩余有空笼的列数量不同,所以做每一个决策的概率不同。
那么有没有办法使概率相同呢?
如果一直没有一列的鸽笼被用完,每一次决策的概率就一直是相同的,每一种选择序列出现的概率也都是相同的。
于是我们需要将“这一列最后被用完”转化为“这一列最先被用完,其他的列都没用完”,显然是容斥。
一通乱容斥后得到:ans(i)=1−∑S(−1)∣S∣+1g(i,S)ans(i)=1-\sum_{S} (-1)^{|S|+1} g(i,S)ans(i)=1−∑S(−1)∣S∣+1g(i,S),其中g(i,S)g(i,S)g(i,S)表示,SSS集合里的列都晚于iii被用完的概率,显然SSS集合外的列都不用考虑了。
生成一个管理员的选择序列,iii出现aia_iai次,其他的jjj都不能出现大于等于aja_jaj次,且最后一个元素是iii,长度为LLL(不一定是N−1N-1N−1,因为当满足“SSS集合里的列都晚于iii被用完”条件后,之后管理员的选择无关紧要了)。这样的序列个数是可以算出来的,并且每个长度为LLL的序列出现的概率都是1∣S∣+1\frac{1}{|S|+1}∣S∣+11。
序列只有长度是有用信息,用DP,设f(i)f(i)f(i)表示长度为iii的以上要求的序列的个数,枚举集合SSS中每一个元素,在序列中出现了多少次,即可O(n4)O(n^4)O(n4)地DP。
总复杂度O(2nn4)O(2^n n^4)O(2nn4)跑不满,期望得分30分。
算法2
复杂度瓶颈在于要枚举集合,但实际上一个集合所带来的有用的信息只有集合大小和管理员选择序列长度LLL。所以干脆每算一个答案ans(i)ans(i)ans(i),做一遍DP,设f(i,j)f(i,j)f(i,j)表示当前集合大小为iii,序列长度为jjj的方案数,枚举除了iii外的每一列加不加入集合,加入多少个进序列即可。
复杂度O(n6)O(n^6)O(n6)跑不满,可能能拿100分。
算法3
算法2的复杂度瓶颈在于,做iii列为有空笼列的答案时,DP不能把iii给考虑进去,可考虑其他列的步骤是重复的。所以先做一次所有列都考虑的DP,然后对于每个iii,逆向DP除去第iii列的贡献即可。
复杂度O(n5)O(n^5)O(n5)跑不满,期望得分100分。
代码
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=998244353;
int n,all;
int a[32],C[905][905],inv[32],bin[32][905],g[32][905],tmp[32][905];
int qm(int x) {return x>=mod?x-mod:x;}
void prework() {
for(RI i=0;i<=all;++i) {
C[i][0]=1;
for(RI j=1;j<=i;++j) C[i][j]=qm(C[i-1][j-1]+C[i-1][j]);
}
inv[1]=1;for(RI i=2;i<=n;++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
for(RI i=1;i<=n;++i) {
bin[i][0]=1;
for(RI j=1;j<=all;++j) bin[i][j]=1LL*bin[i][j-1]*inv[i]%mod;
}
}
void work() {
int lim=0;g[0][0]=1;
for(RI i=1;i<=n;++i) {
lim+=a[i]-1;
for(RI j=i;j>=1;--j)
for(RI k=lim;k>=0;--k)
for(RI t=0;t<a[i]&&t<=k;++t)
g[j][k]=qm(g[j][k]-1LL*g[j-1][k-t]*C[k][t]%mod+mod);
}
for(RI i=1;i<=n;++i) {
int ans=0;
for(RI j=0;j<n;++j)
for(RI k=0;k<=lim-a[i]+1;++k) tmp[j][k]=g[j][k];
for(RI j=1;j<n;++j)
for(RI k=0;k<=lim-a[i]+1;++k)
for(RI t=0;t<a[i]&&t<=k;++t)
tmp[j][k]=qm(tmp[j][k]+1LL*tmp[j-1][k-t]*C[k][t]%mod);
for(RI j=0;j<n;++j)
for(RI k=0;k<=lim-a[i]+1;++k)
ans=qm(ans+1LL*tmp[j][k]*C[k+a[i]-1][k]%mod*bin[j+1][k+a[i]]%mod);
printf("%d ",ans);
}
}
int main()
{
scanf("%d",&n);
for(RI i=1;i<=n;++i) scanf("%d",&a[i]),all+=a[i];
prework(),work(),puts("");
return 0;
}