BZOJ 2339 【HNOI2011】 卡农
题目链接:卡农
听说这道题是经典题?
首先明确一下题意(我在这里纠结了好久):有\(n\)个数,要求你选出\(m\)个不同的子集,使得每个数都出现了偶数次。无先后顺序。
这道题就是一道数学题。显然我们可以强制有先后顺序,只需要在最后除掉一个\(m!\)即可。令\(f_i\)表示选出\(i\)个子集的方案数,我们来考虑一下怎么算。
由于总的方案数很好计算,选出\(i\)个子集的方案就是\(A^{i-1}_{2^n-1}\),因为一旦选出了前\(i-1\)个,第\(i\)个就已经确定了。
我们这样选,可以保证每个数出现了偶数次,但是并不能够保证选出的不是空集以及集合不重复。所以我们来看看不合法的情况有多少。
第一种情况,如果前\(i-1\)个就是一个合法方案,那么第\(i\)个只能是空集。这种情况显然不合法,方案数是\(f_{i-1}\)。
第二种情况,第\(i\)个集合和之前任意一个冲突了就不行。由于另外还剩\(i-2\)个集合未确定,所以这部分方案数为\((i-1)f_{i-2}(2^n-1-(i-2))\)。第\(i\)个集合可选的方案数为\(2^n-1-(i-2)\),然后和另外\(i-2\)个一起排列一下还要乘上\(i-1\)。
所以这道题就做完了。
下面贴代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) #define maxn 1000010 #define mod 100000007 using namespace std; typedef long long llg; int n,m,nci; llg f[maxn],g[maxn],jie; llg mi(llg a,int b){ llg s=1; while(b){ if(b&1) s=s*a%mod; a=a*a%mod; b>>=1; } return s; } int main(){ File("a"); scanf("%d %d",&n,&m); nci=(mi(2,n)-1+mod)%mod; g[0]=jie=1; for(int i=1;i<=m;i++) g[i]=g[i-1]*(nci-i+1)%mod; for(int i=1;i<=m;i++) jie*=i,jie%=mod; for(int i=3;i<=m;i++){ f[i]=g[i-1]-f[i-1]; f[i]-=(i-1)*f[i-2]%mod*(nci-i+2)%mod; f[i]%=mod; if(f[i]<0) f[i]+=mod; } printf("%lld",f[m]*mi(jie,mod-2)%mod); return 0; }