[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)\) 的算法。
小结:
- 理清楚问题到底有哪些限制,然后一条条地解决;
- 将平时常用的技巧联系起来,这里从二进制角度看就容易想到异或,从而得知最后一个片段可以由前 \(k-1\) 得到;
- 在正面解决不方便的时候,一定要多多尝试能不能用容斥原理或者减去反面情况;
代码
#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;
}