UOJ #390 【UNR #3】百鸽笼 容斥+DP

博客围绕管理员选列问题展开,分析了四种算法。算法0未考虑决策概率不同而错误;算法1用容斥原理使概率相同,结合DP计算,复杂度O(2^n n^4);算法2优化枚举集合,复杂度O(n^6);算法3进一步优化,先全列DP再逆向除去单列贡献,复杂度O(n^5)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目分析

算法0

每个管理员选哪一列,将构成一个长度为N−1N-1N1的序列,序列的种数可以通过经典的将aaa个相同元素插入到一个没有该元素的长度为bbb的序列里问题,轻松求出。

若一列iii要求有空笼,则标号iii只出现ai−1a_i-1ai1次,每种序列出现的概率都是一样的,一下子就算出来啦!
...
期望得分: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)=1S(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-1N1,因为当满足“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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值