【HDU】4390 Number Sequence 容斥原理

传送门:【HDU】4390 Number Sequence


题目分析:这题理解好累啊T . T

首先我们将所有的bi拆成质因数,然后统计所有质因数的个数。对于原题,我们可以假设左边是N个盒子,每种质因数就是一种颜色的球,每种质因数的个数就是每种颜色的球的个数,那么题目转化为求将所有球放入N个盒子中并且每个盒子至少有一个球的方案数。

方案数很明显不能直接求,但是通过容斥原理我们可以间接的求出答案。

令c[ i ][ j ]为从i个中选j个的组合数。

设一共有k种质因数,b[ i ]即第i种质因数的个数。

首先,我们先不管题目中每个盒子必须至少放一个球的限制条件,那么方案数ans即c[ n + b[1] - 1][n - 1] * c[ n + b[2] - 1][n - 1] * ... * c[ n + b[k] - 1][n - 1]。

令f ( n , k ) = c[ n + b[1] - 1][n - 1] * c[ n + b[2] - 1][n - 1] * ... * c[ n + b[k] - 1][n - 1]。

然后,我们可以先假设确定了一个盒子为空,那么在剩下的盒子中放球的方案数为c[ n ][ 1 ] * f ( n - 1 , k )。

再假设确定了两个盒子为空,那么在剩下的盒子中放球的方案数为c[ n ][ 2 ] * f ( n - 2 , k )。

......以此类推。

最后的答案ans = sum { ( -1 ) ^ i * c[ n ][ i ] * f ( n - i , k ) | 0 <= i < n }

为什么这样可以呢?其实手推一下应该还是能发现规律的:

假设在只有一种球并空了x个盒子的方案数为res,则res - c[ n ][ 1 ] * c[ n - 1 ][ x - 1 ] + c[ n ][ 2 ] * c[ n - 2 ][ x - 2 ] - ... + ( -1 ) ^ x * c[ n ][ x ] * c[ n - x ][ 0 ] = 0。

多种球一个道理。

或许可能会觉得这样不够严谨,下面还有一种证明方法,是我在别人的blog上看到的:

设f[ i ]为没有限制条件时在i个盒子内放球的方案数,g[ i ]为i个盒子都至少有一个球的方案数。

即最终要求g[ n ]。

我们假设n = 3,则:

f[ 3 ] = g[ 3 ] + c[ 3 ][ 1 ] * g[ 2 ] + c[ 3 ][ 2 ] * g[ 1 ]

f[ 2 ] = g[ 2 ] + c[ 2 ][ 1 ] * g[ 1 ]

f[ 1 ] = g[ 1 ]

化简可得:g[ 3 ] = f[ 3 ] - c[ 3 ][ 1 ] * f[ 2 ] + c[ 3 ][ 2 ] * f[ 1 ]

通过数学归纳法可以得到g[ n ] = sum { ( -1 ) ^ i * c[ n ][ i ] * f[ n - i ] | 0 <= i < n }


代码如下:


#include <map>
#include <cmath>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REP( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define FOR( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define REV( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define CLR( a , x ) memset ( a , x , sizeof a )
#define CPY( a , x ) memcpy ( a , x , sizeof a )

typedef long long LL ;

const int MAXN = 10005 ;
const int mod = 1e9 + 7 ;

int a[MAXN] , cnt ;
int c[400][400] ;
int b[MAXN] ;
int n ;

void fun () {
	FOR ( i , 0 , 400 ) {
		c[i][i] = c[i][0] = 1 ;
		REP ( j , 0 , i ) {
			c[i][j] = ( c[i - 1][j - 1] + c[i - 1][j] ) % mod ;
		}
	}
}

void divide ( int x ) {
	for ( int i = 2 ; i * i <= x ; ++ i ) {
		while ( x % i == 0 ) {
			a[++ cnt] = i ;
			x /= i ;
		}
	}
	if ( x > 1 ) a[++ cnt] = x ;
}

int unique ( int n ) {
	CLR ( b , 0 ) ;
	sort ( a + 1 , a + n + 1 ) ;
	a[0] = 0 ;
	int cnt = 0 ;
	FOR ( i , 1 , n ) {
		if ( a[i] != a[cnt] ) a[++ cnt] = a[i] ;
		++ b[cnt] ;
	}
	return cnt ;
}

void solve () {
	int x ;
	cnt = 0 ;
	REP ( i , 0 , n ) {
		scanf ( "%d" , &x ) ;
		divide ( x ) ;
	}
	cnt = unique ( cnt ) ;
	LL ans = 0 ;
	REP ( i , 0 , n ) {
		LL tmp = c[n][i] ;
		FOR ( j , 1 , cnt ) tmp = ( tmp * c[n - i + b[j] - 1][n - i - 1] ) % mod ;
		if ( i & 1 ) ans = ( ans - tmp + mod ) % mod ;
		else ans = ( ans + tmp ) % mod ;
	}
	printf ( "%I64d\n" , ans ) ;
}

int main () {
	fun () ;
	while ( ~scanf ( "%d" , &n ) ) solve () ;
	return 0 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值