[HNOI2011]卡农

题目描述

众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。他将声音分成 n 个音阶,并将音乐分成若干个片段。音乐的每个片段都是由 1 到 n 个音阶构成的和声,即从 n 个音阶中挑选若干个音阶同时演奏出来。为了强调与卡农的不同,他规定任意两个片段所包含的音阶集合都不同。同时为了保持音乐的规律性,他还规定在一段音乐中每个音阶被奏响的次数为偶数。现在的问题是:小余想知道包含 m 个片段的音乐一共有多少种。两段音乐 a 和 b 同种当且仅当将 a 的片段重新排列后可以得到 b。例如:假设 a

为{{1,2},{2,3}},b 为{{3,2},{2,1}},那么 a 与 b 就是同种音乐。由于种数很多,你只需要

输出答案模 100000007(质数)的结果。

输入输出格式

输入格式:

从文件input.txt中读入数据,输入文件仅一行,具体是用空格隔开的两个正整数n和m,分别表示音阶的数量和音乐中的片段数。20%的数据满足n,m≤5,50%的数据满足n,m≤3000,100%

的数据满足n,m≤1000000。

输出格式:

输出文件 output.txt 仅包含一个非负整数,表示音乐的种数模 100000007 的结果。【输入输出样例】

输入输出样例

输入样例#1:

2 3

输出样例#1:

1

说明

样例解释:音乐为{{1},{2},{1,2}}


题解

计数DP+容斥
反正计数题我肯定只能抄题解
首先发现题目的要求有两个:

1.每段不能相同
2.每个音阶的总出现次数一定是偶数

首先题目要求无序的集合
处理无序集合很麻烦,可以直接按照有序集合处理最后再除以\(m!\)
可以发现这个每个音阶出现总次数是偶数的限制考虑起来很麻烦
这种情况下直接递推似乎很不可行
可以考虑一个容斥的\(DP\)
\(f_i\)表示选择\(i\)段并且使这\(i\)段已经合法的方案数
那么这样我们怎么处理偶数的限制呢?
考虑到了第\(m\)
那么我们已经选好了前\(m-1\)段,那么对于第\(m\)段我们能选择的东西其实就已经确定了,就是那些出现次数为奇数的音符
所以我们也可以这样来考虑求\(f_i\)
首先选择\(i\)段的总方案数为\(A_{i}^{2^n-1}\)
但是注意我们实际上对于\(f_i\)是要确定前\(i-1\)段选啥,因为确定了前\(i-1\)段选啥第\(i\)段就已经确定了,所以实际上我们要考虑的是\(A_{i-1}^{2^n-1}\)
然后考虑去除不合法情况:

1.第\(i\)段是空集的方案数:\(f_{i-1}\)
2.第\(i\)段和前面选择的某一段重复了:\((i-1)\times f_{i-2} \times (2^n-1-(i-2))\)

所以递推式就是\(f_i=A_{i-1}^{2^n-1}-f_{i-1}-(i-1)\times f_{i-2} \times (2^n-1-(i-2))\)

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int M = 1000005 ;
const int mod = 1e8 + 7 ;
using namespace std ;

int n , m , pw2 , fac_m , A[M] , f[M] ;
inline int Fpw(int Base , int k) {
	int temp = 1 ;
	while(k) {
		if(k & 1) temp = 1LL * temp * Base % mod ;
		Base = 1LL * Base * Base % mod ; k >>= 1 ;
	}
	return temp ;
}
int main() {
	scanf("%d%d",&n,&m) ; 
	pw2 = Fpw(2 , n) - 1 ; A[0] = 1 ; fac_m = 1 ;
	for(int i = 1 ; i <= m ; i ++) {
		A[i] = 1LL * A[i - 1] * (pw2 - i + 1) % mod ;
		fac_m = 1LL * fac_m * i % mod ;
	}
	f[0] = 1 ; f[1] = 0 ;
	for(int i = 2 ; i <= m ; i ++) {
		f[i] = ( A[i - 1] - f[i - 1] ) % mod - 1LL * (i - 1) * f[i - 2] % mod * (pw2 - (i - 2)) % mod ;
		f[i] = (f[i] % mod + mod) % mod ;
	}
	printf("%lld\n",1LL * f[m] * Fpw(fac_m , mod - 2) % mod) ;
	return 0 ;
}
posted @ 2019-04-26 09:42  beretty  阅读(292)  评论(0编辑  收藏  举报