[HNOI2011]卡农

题目

点这里看题目。

分析

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

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

看一下问题的限制:

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

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

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

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

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

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

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

  • 不合第二条:此时前 \(k-1\) 只有一个会和第 \(k\) 个相同,枚举这一个的位置和具体值,得到方案数:\(f_{k-2}\times (k-1)\times (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( '\n' );
	return 0;
}
posted @ 2021-08-10 19:23  crashed  阅读(55)  评论(0编辑  收藏  举报