zoukankan      html  css  js  c++  java
  • [HNOI2011]卡农

    题目

    点这里看题目。

    分析

    一个片段就是 ({1,2,dots,n}) 的一个非空子集,所以片段共有 (2^n-1) 个;

    问题相当于求片段集合的大小为 (m),且每个音符最终出现偶数次的子集数量。

    看一下问题的限制:

    • 所有片段非空
    • 集合中不存在相同的两个片段
    • 集合中每个元素总共出现偶数次
    • 最终集合无序

    无序的限制很好解决。由于最终片段互不相同,因此我们可以计算所有的序列数量,最后除掉 (m!)

    现在限制变成了两个,我们不妨尝试一些常用的计数方法。例如,我们可以使用递推:设 (f_k) 为包含 (k)非空不重片段序列数量。

    寻找递推式的时候,直接计算难度比较大——简单的想法是反过来计算不合法的数量;

    首先需要找出总量:由于元素出现偶数次,就相当于二进制表示集合时,所有集合异或和为 0,那么当前 (k-1) 个片段确定之后,第 (k) 个片段自然也被确定了,因此总方案数为 (inom{2^{n}-1}{k-1} imes (k-1)!)

    其次除去不符合要求的量:

    • 不合第一条:容易得到方案数为 (f_{k-1})

    • 不合第二条:此时前 (k-1) 只有一个会和第 (k) 个相同,枚举这一个的位置和具体值,得到方案数:(f_{k-2} imes (k-1) imes (2^n-k+1))

      此时与 (k) 重复的元素不会为空,因此不会和 (f_{k-1}) 算重;

    于是我们得到了 (O(n)) 的算法。

    小结:

    1. 理清楚问题到底有哪些限制,然后一条条地解决
    2. 将平时常用的技巧联系起来,这里从二进制角度看就容易想到异或,从而得知最后一个片段可以由前 (k-1) 得到;
    3. 在正面解决不方便的时候,一定要多多尝试能不能用容斥原理或者减去反面情况

    代码

    #include <cstdio>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    const int mod = 1e8 + 7;
    const int MAXN = 1e6 + 5;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0; char s = getchar(); int f = 1;
    	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
    	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    	x *= f;
    }
    
    template<typename _T>
    void write( _T x )
    {
    	if( x < 0 ) putchar( '-' ), x = -x;
    	if( 9 < x ) write( x / 10 );
    	putchar( x % 10 + '0' );
    }
    
    int fac[MAXN], ifac[MAXN];
    int dp[MAXN];
    
    int N, M;
    
    inline int Qkpow( int, int );
    inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
    inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
    inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
    inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
    
    inline int Qkpow( int base, int indx )
    {
    	int ret = 1;
    	while( indx )
    	{
    		if( indx & 1 ) ret = Mul( ret, base );
    		base = Mul( base, base ), indx >>= 1;
    	}
    	return ret;
    }
    
    void Init( const int n )
    {
    	fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
    	ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
    }
    
    int main()
    {
    	read( N ), read( M ), Init( M );
    	int all = Sub( Qkpow( 2, N ), 1 );
    	int down = 1; dp[0] = 1;
    	rep( i, 1, M )
    	{
    		dp[i] = Sub( down, dp[i - 1] );
    		if( i > 1 ) dp[i] = Sub( dp[i], Mul( dp[i - 2], Mul( Sub( all, i - 2 ), i - 1 ) ) );
    		down = Mul( down, all - i + 1 );
    	}
    	write( Mul( ifac[M], dp[M] ) ), putchar( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    vue 组件传值
    ES6 解构赋值
    JS filter的使用
    FormData实现文件上传
    vue+element 表格导出Excel文件
    vue2.0 element-ui中input的@keyup.native.enter='onQuery'回车查询刷新整个表单的解决办法
    vue2.0 element-ui中el-upload的before-upload方法返回false时submit()不生效解决方法
    JavaScript正则表达式检验手机号码、邮箱、ip地址等
    Vue 2.0 pagination分页组件
    angular环境
  • 原文地址:https://www.cnblogs.com/crashed/p/15125479.html
Copyright © 2011-2022 走看看