D. Bouquet (朴素求组合数,补集思想)
题意:
从 n 种花里挑,每种花只有一个,最后花的数量不能是 a 或者 b,求所有方案数 mod 1e9+7。
数据范围:
2 ≤ n ≤
1 ≤ a < b ≤ min(n,2 ∗)
思路:
从n个数中选 1 ~ n(不含a,b)个数,即C(1~n) - C(n,a) - C(n,b)。因为C(n,0) + C(n,1) + C(n,2) + ……+ C(n,n) = 2^n,所以答案为 - C(n,a) - C(n,b) - 1.
因为n很大,直接用基本公式算:C(a,b) = 。用快速幂预处理阶乘和逆元。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int mod = 1e9 + 7;
int n, a, b;
//快速幂
int qmi(int a, int k, int mod)
{
int res = 1;
while (k)
{
if (k & 1)
res = res * a % mod;
a = a * a % mod;
k >>= 1;
}
return res;
}
void solve()
{
cin >> n >> a >> b;
int c1 = 1, c2 = 1;
for (int i = 1; i <= a; i++)
c1 = c1 * (n - a + i) % mod * qmi(i, mod - 2, mod) % mod; //c1=C(n,a),分子从1*2*……*(n-a+1),总共a个数;分母即a!,总共乘a个数的逆元
for (int i = 1; i <= b; i++)
c2 = c2 * (n - b + i) % mod * qmi(i, mod - 2, mod) % mod; //c2=C(n,b)
cout << (qmi(2, n, mod) - c1 - c2 - 1 + 2 * mod) % mod << endl;
}
signed main()
{
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
E. Roaming (组合计数,隔板法)
题意:
有 n 个房间,每个房间 1 个人,定义一个事件是某个人在 n 个房间中任意移动一次,但不能待在原地, 询问 k 次事件后,各房间有多少种情况,答案 mod 1e9+7
数据范围:
3 ≤ n ≤ 2 ∗
2 ≤ k ≤
思路:
根据 k 次移动后这 n 个房间中空房间的个数为划分依据。设空房间的个数为 m(0~min(k,n-1)),m 个房间为空,说明 n 个人都在 n-m 个房间中,也就是将 n 个人分成 n-m 块。等价于:对于 n 个小球中间的 n-1 个位置放置 n-m-1 个隔板,形成 n-m 个空间。
在n个房间中随机寻找m(m为0~min(k,n-1))个空房间 —— C(n,m);
在n-1个位置放n-m-1个隔板 —— C(n-1,n-m-1)。
数据不是很大,而且模数为质数,预处理逆元,阶乘即可。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int N = 200010, mod = 1e9 + 7;
int n, k;
int fact[N], infact[N]; //fact数组存阶乘,infact存逆元的阶乘
//快速幂
int qmi(int a, int k, int p)
{
int res = 1;
while (k)
{
if (k & 1)res = res * a % p;
a = a * a % p;
k >>= 1;
}
return res;
}
void solve()
{
cin >> n >> k;
//预处理所有的阶乘与逆元
fact[0] = infact[0] = 1;
for (int i = 1; i <= n; i++)
{
fact[i] = fact[i - 1] * i % mod;
infact[i] = infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
int res = 1; //空房间的个数为0的情况
for (int i = 1; i <= min(k, n - 1); i++) //当k>n-1时,最多空房间的个数不是k,而是n-1
{
//C(n,m)*C(n-1,n-m-1)
res = (res + (fact[n] * infact[i] % mod * infact[n - i] % mod) * (fact[n - 1] * infact[n - i - 1] % mod * infact[i] % mod)) % mod;
}
cout << res << endl;
}
signed main()
{
int t = 1;
//cin >> t;
while (t--)
{
solve();
}
return 0;
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
吐槽:组合数学模板时根据不同题意会有很多不同的处理方法,题目注意点:
1.C(n,0)+C(n,1)+C(n,2)+……+C(n,n)=2^n
2.对于求个数较少的组合数,像D题只需求C(n,a)和C(n,b),直接用两个循环即可(当然,本题a,b的最大值过大,也不能用数组存)。
对于求多个组合数,像E题可以用一个循环算出阶乘fact[],阶乘的逆元infact[]。求组合数直接用两数组套即可。
3.对于答案很大要mod数时,注意至少每两数乘相乘就要mod一次,防止越界。