其他分享
首页 > 其他分享> > AtCoder Regular Contest 096

AtCoder Regular Contest 096

作者:互联网

C.Everything on It

题目描述

点此看题

解法

先思考一个简化的问题,如果要求是 \(1,2...n\) 都在其中至少出现 \(1\) 次我们会怎么做?直接上容斥,我们枚举出现次数 \(=0\) 数的个数,然后其他的乱选即可。

上述方法是可扩展的,我们可以枚举出现次数 \(\leq 1\) 数的个数,那么可以写出式子:

\[\sum_{i=0}^n (-1)^i\cdot {n\choose i}\cdot2^{2^{n-i}}\cdot f(i) \]

其中 \(f(i)\) 表示钦定 \(i\) 个数出现次数 \(\leq 1\) 的方案数,关键的 \(\tt observation\) 是涉及到的集合数量不会超过 \(i\) 个,而且知道了集合数量方案数是会好算很多的。所以再处理一个 \(g(i,j)\) 表示用 \(j\) 个集合去覆盖 \(i\) 个数,每个数最多被覆盖一次的方案数,转移:

\[g(i,j)=g(i-1,j-1)+(j+1)\cdot g(i-1,j) \]

转移的含义是考虑当前的数 新增一个集合 \(/\) 填入以前的集合(\(j\) 种方案)\(/\) 不覆盖,那么 \(f(i)\) 就很容易表示了:

\[f(i)=\sum_{j=0}^i g(i,j)\cdot 2^{j(n-j)} \]

解释一下后面的 \(2^{j(n-j)}\),实际上就是把未钦定的元素考虑进去,它们加入这些集合是任意的。

只能说这道题完全在我的能力范围以内,时间复杂度 \(O(n^2)\)

#include <cstdio>
const int M = 3005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,g[M][M],C[M][M];
void add(int &x,int y) {x=(x+y)%m;}
int qkpow(int a,int b,int p)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%p;
		a=a*a%p;
		b>>=1;
	}
	return r;
}
signed main()
{
	n=read();m=read();g[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			add(C[i][j],C[i-1][j-1]+C[i-1][j]);
	}
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++)
		{
			if(j) add(g[i][j],g[i-1][j-1]);
			add(g[i][j],(j+1)*g[i-1][j]);
		}
	for(int i=0;i<=n;i++)
	{
		int z=C[n][i]*qkpow(2,qkpow(2,n-i,m-1),m)%m;
		int f=0,x=qkpow(2,n-i,m);if(i&1) z=m-z;
		for(int j=0,y=1;j<=i;j++,y=y*x%m)
			add(f,g[i][j]*y);
		add(ans,f*z);
	}
	printf("%lld\n",(ans+m)%m);
}

标签:AtCoder,int,sum,个数,096,cdot,Regular,read,集合
来源: https://www.cnblogs.com/C202044zxy/p/16076087.html