zoukankan      html  css  js  c++  java
  • [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) imes f_{i-2} imes (2^n-1-(i-2)))

    所以递推式就是(f_i=A_{i-1}^{2^n-1}-f_{i-1}-(i-1) imes f_{i-2} imes (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
    ",1LL * f[m] * Fpw(fac_m , mod - 2) % mod) ;
    	return 0 ;
    }
    
  • 相关阅读:
    centos6.4下django1.11.3项目的部署
    inner join和left join 、right join 的区别?
    php中的对象赋值
    windows下Call to undefined function curl_init() error问题
    include和require的区别误区
    第一车网笔试题
    借贷宝笔试题
    40斤西瓜3人分,求分法
    走楼梯算法
    ip地址分类
  • 原文地址:https://www.cnblogs.com/beretty/p/10772509.html
Copyright © 2011-2022 走看看