[Luogu P3214] [BZOJ 4339] [HNOI2011]卡农

洛谷传送门
BZOJ传送门

题目描述

众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。他将声音分成 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 { { 3 , 2 } , { 2 , 1 } } \{\{3,2\},\{2,1\}\} {{3,2},{2,1}},那么 a a a b b b 就是同种音乐。由于种数很多,你只需要

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

输入输出格式

输入格式:

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

的数据满足 n , m ≤ 1000000 n,m≤1000000 n,m1000000

输出格式:

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

输入输出样例

输入样例#1:
2 3
输出样例#1:
1

解题分析

题目无非是让我们在选子集的时候满足三个条件:

  1. 选的每个元素都出现偶数次
  2. 选的子集不为空
  3. 不能选重复的子集

假设我们正在考虑计算 d p [ i ] dp[i] dp[i]。如果只要求满足第一个要求, 直接确定 i − 1 i-1 i1个之前选的子集, 第 i i i个子集就唯一确定了, 所以这里算出来的方案数是 ( 2 n − 1 i − 1 ) × ( i − 1 ) \binom{2^n-1}{i-1}\times (i-1) (i12n1)×(i1)

然后考虑为空的限制, 发现当且仅当前 i − 1 i-1 i1个本身就满足条件才会导致第 i i i个为空, 所以减去 d p [ i − 1 ] dp[i-1] dp[i1]即可。

最后一个限制, 发现只可能是第 i i i个和前 i − 1 i-1 i1个中的一个重复了, 那么剩下的 i − 2 i-2 i2个就是合法的了, 也就是 d p [ i − 2 ] dp[i-2] dp[i2]。 重复的一个的选法有 ( 2 n − 1 ) − ( i − 2 ) (2^n-1)-(i-2) (2n1)(i2), 然后重复的那个子集在 i − 1 i-1 i1个子集中有 i − 1 i-1 i1个不同的位置, 因此这一部分减去的贡献是 d p [ i − 2 ] × ( i − 1 ) × ( 2 n − 1 − ( i − 2 ) ) dp[i-2]\times (i-1)\times (2^n-1-(i-2)) dp[i2]×(i1)×(2n1(i2))

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <cstdlib>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 1005000
#define MOD 100000007
int A[MX], dp[MX];
int pw, n, m, fac = 1;
IN int fpow(R int base, R int tim)
{
	int ret = 1;
	W (tim)
	{
		if (tim & 1) ret = 1ll * ret * base % MOD;
		base = 1ll * base * base % MOD, tim >>= 1;
	}
	return ret;
}
int main(void)
{
	scanf("%d%d", &n, &m);
	pw = (fpow(2, n) - 1 + MOD) % MOD;
	A[0] = 1;
	for (R int i = 1; i <= m; ++i) A[i] = 1ll * (pw - i + 1 + MOD) % MOD * A[i - 1] % MOD, fac = 1ll * fac * i % MOD;
	dp[0] = 1, dp[1] = 0;
	for (R int i = 2; i <= m; ++i) dp[i] = ((A[i - 1] - dp[i - 1] - 1ll * dp[i - 2] * (i - 1) % MOD * (pw - (i - 2)) % MOD) % MOD + MOD) % MOD;
	printf("%lld\n", 1ll * dp[m] * fpow(fac, MOD - 2) % MOD);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值