[HNOI2011]卡农 (数论计数,DP)

题面

原题面

众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。

他将声音分成 n n n 个音阶,并将音乐分成若干个片段。音乐的每个片段都是由 1 1 1 n n n 个音阶构成的和声,即从 n n n 个音阶中挑选若干个音阶同时演奏出来。

为了强调与卡农的不同,他规定任意两个片段所包含的音阶集合都不同。同时为了保持音乐的规律性,他还规定在一段音乐中每个音阶被奏响的次数为偶数。(注:“一段音乐”指整个曲子)

现在的问题是:小余想知道包含 m m m 个片段的音乐一共有多少种。
两段音乐 a a a b b b 同种当且仅当将 a a a 的片段重新排列后可以得到 b b b。例如:假设 a a a { { 1 , 2 } , { 2 , 3 } } \{\{1,2\},\{2,3\}\} {{1,2},{2,3}} b b b { { 2 , 3 } , { 1 , 2 } } \{\{2,3\},\{1,2\}\} {{2,3},{1,2}},那么 a a a b b b 就是同种音乐。

答案对 1 0 8 + 7 10^8+7 108+7 取模。

输入格式

仅一行两个正整数 n , m n,m n,m

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1

2 3

输出 #1

1

说明/提示

【数据范围】
对于 20 % 20\% 20% 的数据, 1 ≤ n , m ≤ 5 1\le n,m \le 5 1n,m5
对于 50 % 50\% 50% 的数据, 1 ≤ n , m ≤ 3000 1\le n,m \le 3000 1n,m3000
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 1 0 6 1\le n,m \le 10^6 1n,m106

【样例解释】
音乐为 { { 1 } , { 2 } , { 1 , 2 } } \{\{1\},\{2\},\{1,2\}\} {{1},{2},{1,2}}

题解

往容斥上面想,但是别想多了。

看了别人的题解发现其实并不难。

首先应该清楚,由于每个片段不相同,所以题目中关于 “同种音乐” 的限制可以去除掉,最后再乘上 ( m ! ) − 1 (m!)^{-1} (m!)1 就是了。

然后,结合 DP 来容斥,我们相当于要达到三个条件:

  • 每一种音阶出现次数是偶数。
  • 无空集。
  • 不存在两片段相同。

我们令 d p [ i ] \mathrm{dp}[i] dp[i] 表示前 i i i 个片段满足上述条件的方案数。

按照容斥的思路,我们应该先把全集求出来,再依次减去不合法。但是很容易发现,要把不符合第一个条件的去掉极其困难。

我们其实可以先求出只满足第一条的方案总数。有一个发现:如果随意确定了前 i − 1 i-1 i1 个片段,那么为了满足第一个条件,可以直接据此推出第 i i i 个片段。那么我们就可以定这个方案数为: A 2 n − 1 i − 1 A_{2^n-1}^{i-1} A2n1i1 ,意为确定前 i − 1 i-1 i1 个片段。这样做还有一个好处,那就是单独看前 i − 1 i-1 i1 个片段时,都是满足第二条和第三条限制的。

这就给我们的容斥带来了极大的便利。去除空集情况,达到第二条限制,很简单,如果有空集就一定是第 i i i 个片段空集,那么前 i − 1 i-1 i1 个片段就满足了三个条件,因此有空集的不合法情况数为 d p [ i − 1 ] \mathrm{dp}[i-1] dp[i1]

接下来要去除存在片段相同的情况。由于前 i − 1 i-1 i1 个片段没有彼此相同的,因此只能是第 i i i 个片段与前面的片段相同,我们就令其为第 j j j 个片段。我们在 1 1 1 i − 1 i-1 i1 中枚举 j j j ,一共 i − 1 i-1 i1 种情况,再确定 i i i j j j 具体应该为哪一种组合,由于 j j j 在前面 i − 1 i-1 i1 个片段中是独一无二的,所以一共有 2 n − 1 − ( i − 2 ) = 2 n − i + 1 2^n-1-(i-2)=2^n-i+1 2n1(i2)=2ni+1 种选择。

然后把 i i i j j j 同时拿走,剩下的 i − 2 i-2 i2 个位置同时满足三个条件,故片段相同的不合法情况数为 d p [ i − 2 ] ∗ ( i − 1 ) ∗ ( 2 n − i + 1 ) {\rm dp}[i-2]*(i-1)*(2^n-i+1) dp[i2](i1)(2ni+1)

综上,总的转移方程为: d p [ i ] = A 2 n − 1 i − 1 − d p [ i − 1 ] − d p [ i − 2 ] ∗ ( i − 1 ) ∗ ( 2 n − i + 1 ) {\rm dp}[i]=A_{2^n-1}^{i-1}-{\rm dp}[i-1]-{\rm dp}[i-2]*(i-1)*(2^n-i+1) dp[i]=A2n1i1dp[i1]dp[i2](i1)(2ni+1) 。时间复杂度 O ( n ) O(n) O(n)

关于排列数 A 2 n − 1 i − 1 A_{2^n-1}^{i-1} A2n1i1 的计算,它其实就是最多 m − 1 m-1 m1 个数相乘。

CODE

#include<map>
#include<cmath>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 1000005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
void putuint(int x) {
	if(!x) return ;
	putuint(x/10);putchar(x%10+'0');
}
void putint(int x) {if(x==0)putchar('0');if(x<0)putchar('-'),x=-x;putuint(x);}

const int MOD = 100000007;
int n,m,i,j,s,o,k;
int qkpow(int a,int b) {
	int res = 1;
	while(b > 0) {
		if(b & 1) res = res *1ll* a % MOD;
		a = a *1ll* a % MOD; b >>= 1;
	}return res;
}
int fac[MAXN]={1,1},inv[MAXN]={1,1},invf[MAXN]={1,1};
int dp[MAXN];
int main() {
	n = read();m = read();
	for(int i = 2;i <= n||i <= m;i ++) {
		fac[i] = fac[i-1] *1ll* i % MOD;
		inv[i] = (MOD-inv[MOD%i]) *1ll* (MOD/i) % MOD;
		invf[i] = invf[i-1] *1ll* inv[i] % MOD;
	}
	dp[0] = 1;dp[1] = 0;
	int po = qkpow(2,n)-1,A = 1,ful = po;
	for(int i = 2;i <= m;i ++) {
		A = A *1ll* po % MOD;
		(po += MOD-1) %= MOD;
		dp[i] = (0ll+ A +MOD- dp[i-1] +MOD- dp[i-2] *1ll* (i-1) % MOD *1ll* (ful +MOD- (i-2)) % MOD) % MOD;
	}
	int as = dp[m] *1ll* invf[m] % MOD;
	printf("%d\n",as);
	return 0;
}
posted @ 2021-07-29 20:19  DD_XYX  阅读(45)  评论(0编辑  收藏  举报