• [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;
    }
    
  • 相关阅读:
    Object之克隆对象clone 和__clone()函数
    Object之魔术函数__toString() 直接输出对象引用时自动调用
    Object之魔术函数__call() 处理错误调用
    Git关联远程GitHub仓库
    python制作查找单词翻译的脚本
    用python处理文本,本地文件系统以及使用数据库的知识基础
    基于序列化技术(Protobuf)的socket文件传输
    Python核心编程——Chapter16
    gdb初步窥探
    unp学习笔记——Chapter1
  • 原文地址:https://www.cnblogs.com/crashed/p/15125479.html
Copyright © 2020-2023  润新知