1. 程式人生 > 實用技巧 >HDU 5201 The Monkey King

HDU 5201 The Monkey King

題目連結

題意

\(n\)個桃子,分給\(m\)個猴子,每隻猴子分到的桃子數為非負整數。

要求第一隻猴子分到的桃子嚴格多餘剩下\(m-1\)只猴子中任意一隻。求分桃子的方案數。

分析

首先列舉第一隻猴子分到的桃子\(x\)

不考慮限制條件的話,剩餘\(m - 1\)只猴子分\(n - x\)個桃子,其方案數\(P(n - x, m - 1) = C_{n - x + m - 2}^{m - 2}\)

我們在這裡用到了隔板法。

隔板法

\(P(n,m)\)表示把\(n\)個桃子分給\(m\)個猴子,其中每隻猴子都得到非負整數個桃子。

我們新加入\(m\)個桃子,給每個猴子一個,總共有了\(n + m\)

個桃子,每隻猴子分到的桃子數變成正整數個。

\(n + m\)個桃子分成\(m\)份,相當於把\(n + m\)個桃子排成一排,然後選擇\(m - 1\)個斷點,將桃子分成\(m\)段。

任意兩個桃子之間有一個可用的斷點,總共有\(n + m - 1\)個斷點可以選擇。

\(P(n,m) = C_{n + m - 1}^{m - 1}\)

隔板法結束

現在考慮限制條件,即\(m - 1\)只猴子分到的桃子\(c_i < x\)

考慮有幾隻猴子分到的桃子數\(c_i \geq x\)

假設至少\(s\)只猴子不滿足\(c_i < x\)

\(c_1 \geq x , c_2 \geq x, \dots, c_s \geq x, c_{s+1} \geq 0, \dots, c_{m-1} \geq 0\)

這裡強行把\(s\)只猴子排在最前面,實際上是隨機的\(s\)只猴子

\(P(n,m)\)處理的是\(c_1 \geq 0, c_2 \geq 0, \dots, c_{m} \geq 0\)的分法問題,我們需要調整一下上面的式子,讓其變為與\(P\)函式相關的式子。

\(g(n - x, m - 1, s)\)表示\(c_1 \geq x , c_2 \geq x, \dots, c_s \geq x, c_{s+1} \geq 0, \dots, c_{m-1} \geq 0\)的分法。

\(g(n - x, m - 1, s) = g(n - x - s * x, m - 1, 0) = P(n - x - s * x , m - 1)\)

即我們對\(s\)只猴子中的每一隻猴子都拿走\(x\)個桃子,使其變為\(c_i \geq 0\)的形式。同時總桃子數減少\(s * x\)

所以當有\(s\)只猴子不滿足條件時,方案數為\(C_{m-1}^{s} * P(n - x - s * x , m - 1)\)

容斥

答案\(Ans = \sum_{s = 0}^{m - 1} (-1)^{s} C_{m-1}^{s} * P(n - x - s * x , m - 1)\)

答案等於至少\(0\)猴子-至少\(1\)猴子+至少\(2\)猴子......

注意各種不合實際的情況要break

容斥結束

特判\(n=1\)\(m=1\)!


#include<bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 7;
const int N = 1e6 + 50;

int n,m;
int fac[N + 10], inv[N + 10];
int ksm(int x,int y){
	int z = 1;
	while(y){
		if(y & 1) z = 1ll * z * x % mod;
		y >>= 1;
		x = 1ll * x * x % mod;
	}
	return z;
}
int C(int n,int m){
    if(n < m || m < 0 || n < 0) return 0;
    return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int P(int n,int m){
    return C(n + m - 1, m - 1); 
}

signed main(){
    fac[0] = 1; for(int i = 1; i <= N; ++ i) fac[i] = 1ll * fac[i - 1] * i % mod;
    inv[N] = ksm(fac[N], mod - 2); for(int i = N - 1; i >= 0; -- i) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
    
    int T; scanf("%d",&T);
    while(T --){
        scanf("%d%d",&n,&m);
        if(n == 1 || m == 1) { puts("1"); continue; }
        int ans = 0;
        for(int i = 1; i <= n; ++ i){
            if(1ll * m * i - m  + 1 < n) continue;

            int ret = P(n - i, m - 1); int flag = -1;
            for(int j = 1; j < m && 1ll * (j + 1) * i <= n; ++ j){
                if(n - i - 1ll * j * i < 0) break;
                int x = 1ll * C(m - 1, j) * P(n - i - j * i, m - 1) % mod;
                x = x * flag;
                ret = ((ret + x) % mod + mod) % mod;
                flag *= -1;
            }
            ans = (ans + ret) % mod;
        }
        printf("%d\n",ans);
    }   
    return 0;
}