HNOI 2011 卡农 题解

题目传送门

题目大意: 有一个集合 { 1 , 2 , . . . , n } \{1,2,...,n\} {1,2,...,n},你要选出他的若干个子集组成长度为 n n n 的序列,满足:不存在相同的子集;不能有空集;每种元素在所有子集中出现次数总数为偶数。问有多少个不同的序列——两个序列相同当且仅当 A A A 可以重新排列得到 B B B

题解

由于序列中每个子集不同,所以可以先不考虑序列相同的问题,最后使答案除以 m ! m! m! 即可。

f [ k ] f[k] f[k] 表示长度为 k k k 的满足三个要求的序列数量,转移时考虑用总方案数减去不合法方案数:

  1. 总方案数:考虑只满足限制 3 3 3 的序列数,发现当确定了前 k − 1 k-1 k1 个集合后,第 k k k 个集合是唯一的,所以方案数为 A 2 n − 1 k − 1 A_{2^n-1}^{k-1} A2n1k1
  2. 不满足限制 1 1 1 的:如果有一个子集和第 k k k 个 子集相同,那么去掉这两个子集后序列依然合法,方案数为 f [ k − 2 ] f[k-2] f[k2],再考虑这个相同子集的位置,有 k − 1 k-1 k1 个位置可选,最后考虑这个子集是什么,有 2 n − k + 2 2^n-k+2 2nk+2 种选择,方案数为 f [ k − 2 ] × ( k − 1 ) × ( 2 n − k + 2 ) f[k-2]\times(k-1)\times(2^n-k+2) f[k2]×(k1)×(2nk+2)
  3. 不满足限制 2 2 2 的:去掉这个空集依然合法,方案数为 f [ k − 1 ] f[k-1] f[k1]

于是就可以 O ( m ) O(m) O(m) 递推了,代码如下:

#include <cstdio>
#define maxn 1000010
#define mod 100000007

int n,m;
int A[maxn],f[maxn];
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}

int main()
{
	scanf("%d %d",&n,&m);
	if(n<=20&&(1<<n)<m)return printf("0"),0;
	int d=(ksm(2,n)-1+mod)%mod,facm=m;
	A[0]=1;for(int i=1;i<m;i++)A[i]=1ll*A[i-1]*(d-i+1+mod)%mod,facm=1ll*facm*i%mod;
	for(int i=3;i<=m;i++)f[i]=((A[i-1]-f[i-1]+mod)%mod-1ll*f[i-2]*(i-1)%mod*(d-i+2)%mod+mod)%mod;
	printf("%lld",1ll*f[m]*ksm(facm,mod-2)%mod);
}
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页