标签:AtCoder int sum 个数 096 cdot Regular read 集合
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
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。