描述
有M个小孩到公园玩,门票是1元。其中N个小孩带的钱为1元硬币,K个小孩带的钱为2元纸币,而售票员没有零钱。问这些小孩共有多少种排队方法,使得售票员总能找得开零钱。注意:两个小孩,他们的位置互换,也算是一种新的排法。
输入
输入一行,M,N,K(其中M=N+K,M<=20).
输出
输出一行,总的排队方案。
首先应该注意到, 由于售票员没有零钱, 故收的2元纸币只能通过收的1元硬币来找零, 也即是说, 如果1元硬币的数量N, 小于2元纸币的数量K, 肯定不存在合法的排列.
其次, 最先能想到的解法应该是暴力破解, 即将M!种排列组合一一枚举.毫无技术含量的算法, 这里不再详细说明实现.
再深入想一想, 当N==K时, 此题变为将数量相等的1,2两个数进行排列组合, 完全可以用卡特兰数求解,. 当N
现在的问题即什么样的排列是非法的. 可以肯定的是, 非法排列中2的个数一定至少比1的个数多1. 那么我们就取2的个数比1多1的排列。
我们假设:
于是我们可以把非法排队分为 3 部分:
前 2P 个小孩组成一个合法排队,且满足:M’ = 2P,N’ = K’ = P。
于是排队数可以用卡特兰数计算。
第 2P + 1 个小孩要持有 2 元,由于前 2P 个小孩中已经用掉 P 个持有 2 元的小孩,此处还有 K - P 种选择。
最后 R 个小孩的排队方式不影响整体性质,所以全排列。公式为: ∑ P = 0 K K ( P ) A ( N P ) A ( K P ) ( K − P ) R ! ∑ P = 0 K K ( P ) A ( N P ) A ( K P ) ( K − P ) R ! \sum_{P=0}^KK(P)A{(_N^P)}A{(_K^P)}(K-P)R!∑P=0KK(P)A(NP)A(KP)(K−P)R! P=0∑KK(P)A(NP)A(KP)(K−P)R!∑P=0KK(P)A(NP)A(KP)(K−P)R!
合法的排队方法数就等于总的方法数减去非法的方法数:
M ! − ∑ P = 0 K K ( P ) A ( N P ) A ( K P ) ( K − P ) R ! M ! − ∑ P = 0 K K ( P ) A ( N P ) A ( K P ) ( K − P ) R ! M!-\sum_{P=0}^KK(P)A{(_N^P)}A{(_K^P)}(K-P)R!M!−∑P=0KK(P)A(NP)A(KP)(K−P)R! M!−P=0∑KK(P)A(NP)A(KP)(K−P)R!M!−∑P=0KK(P)A(NP)A(KP)(K−P)R!
我们设函数buy(n, k, y), 其中n为1元硬币的数量, k为2元纸币的数量, y为售票员的起始找零, 该函数计算n个1元, k个2元, 起始找零为y元的条件下, 合法的排列数量.
对于buy(n, k, y), y==0时, 不难发现排在第一个的只能是1(n个1, 任取一个, 共n种情况), 如果第一个是2那将无法找零. 此时我们将这个排在第一个的1, 视为下一步递归的初始找零, 因为这是售票员可以直接收走的.那么此时的情况为, n-1个1元硬币, k个2元纸币, 起始找零为1元. 即buy(n-1, k, 1). 也就是说,
b u y ( n , k , 0 ) = n ∗ b u y ( n − 1 , k , 1 ) buy(n, k, 0) = n*buy(n-1, k, 1) buy(n,k,0)=n∗buy(n−1,k,1).
那么当 y ≠ 0 y\not=0 y=0时, 显然1和2都可以排在第一个. 不同之处在于, 1排第一个, 下一步递归的y+1; 2排在第一个, 那么y-1;
总结, 我们得到递归公式如下:
b u y ( n , k , y ) = { n ∗ b u y ( n − 1 , k , 1 ) y = 0 n ∗ b u y ( n − 1 , k , y + 1 ) + k ∗ b u y ( n , k − 1 , y − 1 ) y ≠ 0 buy(n, k, y) = \begin{cases} n*buy(n-1, k, 1) & y = 0 \\ n*buy(n-1, k, y+1)+k*buy(n, k-1, y-1) & y \not= 0 \end{cases} buy(n,k,y)={n∗buy(n−1,k,1)n∗buy(n−1,k,y+1)+k∗buy(n,k−1,y−1)y=0y=0
有了递归公式, 接下来需要解决递归的出口.
显然buy(n, k, y)中的n, k, y都不可能是负数, 也就是说n, k只要有一个递归到0, 就必须结束.
那么我们先考虑 n ≠ 0 , k = 0 n \not= 0, k=0 n=0,k=0. 此时意味着, 只有n个带1元硬币的小孩, 无论怎么排队, 都是合法的, 故 b u y ( n , 0 , y ) = A ( n n ) buy(n, 0, y)=A{(_n^n)} buy(n,0,y)=A(nn)
其次我们考虑 n = 0 , k ≠ 0 n = 0, k \not= 0 n=0,k=0. 此时意味着, 只有k个带2元纸币的小孩, 那么售票员至少需要k元的起始找零, 否则不存在合法排列.故
b u y ( 0 , k , y ) = { 0 y < k A ( k k ) y ≥ k buy(0, k, y) = \begin{cases} 0 & y < k \\ A{(_k^k)} & y \geq k \end{cases} buy(0,k,y)={0A(kk)y<ky≥k
综上,我们总结buy函数的递归公式如下
b u y ( n , k , y ) = { n ∗ b u y ( n − 1 , k , 1 ) y = 0 , n ≠ 0 , k ≠ 0 n ∗ b u y ( n − 1 , k , y + 1 ) + k ∗ b u y ( n , k − 1 , y − 1 ) y ≠ 0 , n ≠ 0 , k ≠ 0 A ( n n ) n ≠ 0 , k = 0 A ( k k ) n = 0 , k ≠ 0 , y ≥ k 0 n = 0 , k ≠ 0 , y < k buy(n, k, y) = \begin{cases} n*buy(n-1, k, 1) & y = 0, n \not= 0, k \not= 0 \\ n*buy(n-1, k, y+1)+k*buy(n, k-1, y-1) & y \not= 0, n \not= 0, k \not= 0 \\ A{(_n^n)} & n \not= 0, k=0 \\ A{(_k^k)} & n=0, k \not= 0, y \geq k \\ 0 & n=0, k \not= 0, y
#include
using namespace std;
// 计算排列数
int a(int a1, int a2){
if (a2 == 0) return 1;
a2--;
int pro = a1;
for (int i = 0; i < a2; i++){
a1--;
pro *= a1;
}
return pro;
}
// 计算组合数
int c(int c1, int c2){
return a(c1, c2) / a(c2, c2);
}
// 计算卡特兰数
int catalan(int n){
return c(2 * n, n) / (n + 1);
}
int main(){
int m, n, k;
cin >> m >> n >> k;
if (n < k) cout << 0;
else{
int sum = 0;
for (int p = 0; p <= k; p++){
int r = m - 2 * p - 1;
int nogood = catalan(p) * a(k, p) * a(n, p) * (k - p) * a(r, r);
sum += nogood;
}
cout << a(m, m) - sum << endl;
}
return 0;
}
#include
using namespace std;
//n的阶乘
int factor(int n){
int fac = 1;
for (int i = 2; i <= n; i++) fac *= i;
return fac;
}
int buy(int n, int k, int y) {
if (0 != n && 0 != k) {
if (0 == y) return n * buy(n - 1, k, 1); // 第一个排队的只能是1元, n种可能, 初始零钱为1
else return n*buy(n-1, k, y+1) + k*buy(n, k-1, y-1); // 两种可能, 第一个排队的可以是1元也可以是2元
}else{ // n 和 k 至少一个为0
if (0==n && 0!=k){ // k!=0
if (y < k) return 0; // k个2元, 至少需要k元的初始找零, 不然无法排队
else return factor(k);
}else if(0==k && 0!=n){
return factor(n);
}else return 0;
}
}
int main(){
int m, n, k;
cin >> m >> n >> k; // 至少需要k个1元, 所以如果n
if (0 == m) cout << 0 << endl;
else if (n < k) cout << 0 << endl;
else cout << buy(n, k, 0) << endl; // 初始找零为0元
return 0;
}