AtCoder - ABC 156 - DE(朴素求组合数+补集思想 | 组合计数+隔板法)

D. Bouquet (朴素求组合数,补集思想)

题意:

从 n 种花里挑,每种花只有一个,最后花的数量不能是 a 或者 b,求所有方案数 mod 1e9+7。

数据范围:

2 ≤ n ≤ 10^{9}

1 ≤ a < b ≤ min(n,2 ∗10^{5})

思路:

从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所以答案为2^{n} - C(n,a) - C(n,b) - 1.

因为n很大,直接用基本公式算:C(a,b) = \frac{a*(a-1)*\cdot \cdot \cdot *(a-b+1)}{1*2*3*\cdot \cdot \cdot *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 ∗ 10^{5}
2 ≤ k ≤ 10^{9}

思路:

根据 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一次,防止越界。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值