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;
}
原文地址:https://www.cnblogs.com/lcf-2000/p/6618151.html