题解 P3214 【[HNOI2011]卡农】

Salamander

2018-02-02 10:09:17

Solution

题目大意: 从集合$\{1,2,...,n\}$中无序地选出$m$个互不相同的非空子集,要求在这些选出的子集中,每个数出现次数为偶数。求方案数。 首先将无序化为有序,最后答案除以$m!$。$dp[i]$表示选出了$i$个子集,并且满足所有限制的方案数。 直接考虑$dp[i-1]$转移到$dp[i]$非常困难,所以考虑补集转换一下,用容斥的方式求$dp[i]$。 首先由于限制了每个数的出现次数为偶数,所以如果前$i-1$个子集确定了,第$i$个的选择是唯一的,第$i$个里面的数必须是前面出现奇数次的那些数。所以在没有限制$i$不和之前相同和非空的情况下,选出$i$个子集的方案数为$A_{2^n-1}^{i-1}$,即从所有非空子集中选出前$i-1$个的排列数。 然后我们减去第$i$个子集为空的情况,方案数为$dp[i-1]$,即前$i-1$个集合自己已经满足了每个数出现次数为偶数的情况。 我们还要减去第$i$个子集和之前某个子集相同的情况。如果我们把相同的两个集合删去,那么剩下的集合肯定合法,所以方案数为$dp[i-2]$。然后第$i$个子集有$2^n-1-(i-2)$种方案,同时和第$i$个子集相同的那个子集的不同位置有$i-1$种,所以第$i$个子集和之前某个子集相同的方案数为$dp[i-2]*(i-1)*(2^n-1-(i-2))$。 所以我们得到了dp的转移: $dp[i]=A_{2^n-1}^{i-1}-dp[i-1]-(dp[i-2]*(i-1)*(2^n-1-(i-2)))$ 初始化$dp[0]=1,dp[1]=0$。 这种方法真的很难想到。 ```cpp #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define For(i,_beg,_end) for(int i=(_beg),i##end=(_end);i<=i##end;++i) #define Rep(i,_beg,_end) for(int i=(_beg),i##end=(_end);i>=i##end;--i) template<typename T>T Max(const T &x,const T &y){return x<y?y:x;} template<typename T>T Min(const T &x,const T &y){return x<y?x:y;} template<typename T>int chkmax(T &x,const T &y){return x<y?(x=y,1):0;} template<typename T>int chkmin(T &x,const T &y){return x>y?(x=y,1):0;} template<typename T>void read(T &x){ T f=1;char ch=getchar(); for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1; for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0'; x*=f; } typedef long long LL; const int maxn=1000010,mod=100000007; int n,m; LL dp[maxn],inv,A[maxn],mx; LL power(LL x,LL y){ LL res=1; for(;y;y>>=1,x=x*x%mod) if(y&1) res=res*x%mod; return res; } int main(){ read(n);read(m); inv=A[0]=1; For(i,2,m) inv=inv*i%mod; inv=power(inv,mod-2); mx=power(2,n)-1; For(i,1,m) A[i]=A[i-1]*(mx-i+1)%mod; dp[0]=1; For(i,2,m){ dp[i]=A[i-1]-dp[i-1]+mod; dp[i]-=dp[i-2]*(i-1)%mod*(mx-(i-2))%mod; dp[i]=(dp[i]%mod+mod)%mod; } printf("%lld\n",dp[m]*inv%mod); return 0; } ```