题目链接:卡农
听说这道题是经典题?
首先明确一下题意(我在这里纠结了好久):有(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; }