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