HDU5201-The Monkey King

題目

題目大意

簡化題意:1~n個不同盒子,放入m個相同小球,盒子可空,但是1號盒子的球數必須嚴格最多,問合法方案數。

分析

簡化問題

如果沒有1號盒子嚴格最多的限制,就變成了一個插板的問題。

也就是x1+x2+xn=mx_1+x_2……+x_n=m這類不定方程求非負整數解的問題,插板法解決。

容斥

如果枚舉猴王(1號盒子)的球數爲i

那麼問題就變成不定方程並且0<=x<=i0<=x<=i,加了一個上限。

上限通常會用容斥轉化成下限,下限繼續插板解決。

枚舉至少k個盒子的球數>=i>=i,然後容斥解決問題。

假設現在是要求至少k個盒子的球數不少於1號盒子,那麼就先選出k個盒子,也就是Cm1kC_{m-1}^{k}

然後向每個選出來的盒子放i個球,剩下的n(k+1)in-(k+1)*i個球隨便放入m1m-1個盒子裏面

然後運用插板法,Cn(k+1)i+m2m2C_{n-(k+1)*i+m-2}^{m-2}種方法。

子問題得證

現在我們解決了已知1號盒子球數i、至少k個盒子的球數不小於i的方案個數。

然後就用容斥解決。

容斥其實和二項式反演有關,有的時候容斥不太好理解或者好想到,就先想到二項式反演再得到容斥的式子。

這裏就先給出容斥的形式,二項式反演後文會講解。

確定i的時候,令f(k)f(k)表示至少k個盒子球數不少於i的方案數

f(k)=Cm1kCn(k+1)i+m2m2f(k)=C_{m-1}^{k}*C_{n-(k+1)*i+m-2}^{m-2}

那麼對於i,答案爲:k=0n(k+1)i>=0(1)kf(k)\sum_{k=0}^{n-(k+1)*i>=0} {(-1)^{k}f(k)}

相關代碼

for(int i=1;i<=n;i++){//枚舉猴王得到的桃子個數 
			long long id=1;
			for(int k=0;n-(k+1)*i>=0;k++){//枚舉至少有k只小猴子得到的桃子個數>猴王 
				ans+=id*C(m-1,k)%mod*C(n-(k+1)*i+m-2,m-2)%mod;
				ans%=mod;
				id=-id;
			}
		}

代碼

下附AC代碼:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int read(){
	char s;
	int x=0,f=1;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
const long long mod=1000000007;
const int N=300000;
long long qpow(long long a,long long b){
	if(b==0)return 1;
	long long rec=qpow(a,b/2)%mod;
	if(b&1)return rec*rec%mod*a%mod;
	return rec*rec%mod;
}
long long calc[N],inv[N];//階乘 階乘逆元 
void init(int n){
	calc[0]=1;
	for(long long i=1;i<=n;i++){
		calc[i]=calc[i-1]*i%mod;
	}
	inv[n]=qpow(calc[n],mod-2);
	for(long long i=n-1;i>=0;i--){
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	return;
}
long long C(int a,int b){//組合數 
	if(b>a)return 0;
	return calc[a]*inv[b]%mod*inv[a-b]%mod;
}
int main(){
	int T=read();
	init(N-5);
	while(T--){
		int n,m;
		n=read(),m=read();//n個桃子,m個猴子 
		if(m==1||n==1){
			puts("1");
			continue;
		}
		long long ans=0;
		for(int i=1;i<=n;i++){//枚舉猴王得到的桃子個數 
			long long id=1;
			for(int k=0;n-(k+1)*i>=0;k++){//枚舉至少有k只小猴子得到的桃子個數>猴王 
				ans+=id*C(m-1,k)%mod*C(n-(k+1)*i+m-2,m-2)%mod;
				ans%=mod;
				id=-id;
			}
		}
		ans=(ans%mod+mod)%mod;
		printf("%lld\n",ans);
	}
}

相關知識

這道題挺綜合的,考了很多組合基本知識

插板法介紹

插板法是解決這麼一類問題:不定方程x1+x2+xn=mx_1+x_2……+x_n=m的非負整數解的問題

其中mn爲正整數

我們把題目轉化成求正整數解,也就是每個數先+1:

x1+1+x2+1+xn+1=m+nx_1+1+x_2+1……+x_n+1=m+n

xi=xi+1x_i=x_i+1

這樣取值範圍也向上加一

那我們把它看成,m+n個相同小球,分成非空的n堆,這樣從左往右n堆分別代表n個x,這就把解轉化爲劃分方式(注意,與第二類斯特林數不同,這個盒子是相同的,沒學過的直接忽略這句話)

那麼由於現在是非空的,就有了m+n1m+n-1個空隙,插入n1n-1個板子,就是組合數Cm+n1n1C_{m+n-1}^{n-1}

這就是插板法解決不定方程問題。

二項式反演介紹

具體內容見:二項式反演

那麼這道題怎麼用二項式反演呢?

其實前面的從m1m-1個盒子裏面選kk個盒子,就蘊含了二項式反演。

當ik確定的時候(一號盒子有i個球,後m-1個盒子裏有k個盒子球數不少於i)

我們令f(k)f(k)表示恰好k個盒子的球數不少於i

g(k)g(k)表示至少k個盒子球數不少於i

那麼這裏的g函數就和容斥的f函數不一樣了。

我就需要欽點k個盒子而不是自由排列組合選出k個盒子。

我欽點了前k個也就是2k+12····{k+1}這幾個盒子每個先放進去i個球,然後剩下的插板解決。

這樣才能保證g函數表示至少k個盒子而不會算重。

g(k)=Cn(k+1)i+m2m2g(k)=C_{n-(k+1)*i+m-2}^{m-2}

g(k)=j=km1Cm1jf(j)g(k)=\sum_{j=k}^{m-1}{C_{m-1}^{j}f(j)}

爲什麼要乘組合數呢?

我們來看:

在這裏插入圖片描述

畫個圖輔助理解:我們看藍色點,表示恰好3個。那麼綠、黃、紅都代表至少兩個。

那麼在f函數裏面,恰好3個的一種方案:藍色點,在至少兩個裏面出現了三次。

因爲我們回到“至少”函數gg的推導:我們欽點一部分作爲一開始各分發i個小球的,剩下球隨便給,黃綠紅分別代表欽點的方案,剩下的一個點代表隨便給球出現的,那麼一個藍色就對應了三種“至少”方案。

因此,可以得到上面的表達式。

那麼上面那個表達式代入二項式反演得到容斥的那個式子:

k=0n(k+1)i>=0(1)kCm1kCn(k+1)i+m2m2\sum_{k=0}^{n-(k+1)*i>=0} {(-1)^{k}C_{m-1}^{k}*C_{n-(k+1)*i+m-2}^{m-2}}

小總結

其實發現二項式反演和容斥的最終結果完全一樣。

容斥選擇至少k個不小於i的盒子直接用組合選。

但是二項式反演要欽點前k個,然後在“至少”和“恰好”之間轉化的時候加上第一個組合數。

看似多此一舉,實則在以後的難題裏運用更廣泛。

總結

這道題不失爲一道組合好題

它綜合運用多種知識,並不難。

這道題用二項式反演其實大材小用了,網上做法很多都是直接容斥。

但是以後遇到更難的題目的時候,想想二項式反演來輔助得到容斥結果也未嘗不可。

推薦題目:已經沒有什麼好害怕的了

附帶此題題解:題解

謝謝觀看,敬請指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章