加法原理#
如果解决某个问题的方案可以分为互不重叠的几类,那么把每一类的方案数加起来,就能得到总的方案数.
乘法原理#
如果把解决某个问题的方案可以分成几个步骤,那么把每一步的方案数依次乘起来,就能得到总的方案数.
排列数和组合数#
排列数
假定一共有 个人,我们要从中选择 个人,让他们排成一排,拍一张照,最多可能有多少种不同的照片呢?
像这样的题目,从 个不同的元素中任取 个元素的所有排列的个数,叫做从 个不同的元素中取出 个元素的排列数,记作
那么, 到底是多少呢?首先,考虑第一个位置,由于有 个人,所以有 种情况,第二个位置呢?由于已经被选掉了一个人,所以第二个位置就有 种选择,以此类推,第 个位置就有 种可能,它们是解决问题的几个步骤,所以使用乘法连接.
一般的:
特殊的:
其中,形如 的运算是阶乘,定义
组合数
假定你到一个洞穴去冒险,洞穴中有 种互不相同的宝物,但是你的袋子只能装下 件,你有多少种拿取的方式呢?
考虑一个有 个元素的集合 ,其拥有 个元素的子集 满足 ,符合条件的 的个数即为组合数,记作
同样的,为了求取 的值,我们可以优先求取 的值,然后,由于选出的 个元素应当不计顺序,故需排除上述选法中的重复:对于上述的每一个集合 ,在排列数中都被正好地重复计算了 次.
所以,一般的:
特殊的,
以下是一个使用费马小定理(参见 取模与逆元)和组合数公式来求组合数的例子:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;
int fac[N], inv[N];
// 快速幂
int fastpow(int a, int p) {
int now = a;
int ans = 1;
while (p >= 1) {
if (p % 2 == 1) {
(ans *= now) %= MOD;
}
(now *= now) %= MOD;
p /= 2;
}
return ans;
}
// 初始化阶乘和对应的逆元
void init()
{
// 初始化阶乘
fac[0] = 1;
for (int i = 1; i <= N - 5; i++) {
fac[i] = fac[i - 1] * i % MOD;
}
// 初始化逆元
for (int i = 1; i <= N - 5; i++) {
inv[i] = fastpow(fac[i], MOD - 2);
}
}
// 计算组合数(n选m)对MOD取模
int C(int n, int m) {
return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
signed main() {
init();
int a, b;
cin >> a >> b;
cout << C(a, b);
return 0;
}c小球八题#
已知有 个球,将其放入 个盒子里,有几种放法呢?根据球或盒子是否相同和是否允许空盒,关于这类题有 8 个变种,我们可以列出以下表格:
| 类型 | 每个球是否完全等价 | 每个盒子是否完全等价 | 是否允许空盒 |
|---|---|---|---|
| A | 是 | 是 | 是 |
| B | 是 | 是 | 否 |
| C | 是 | 否 | 是 |
| D | 是 | 否 | 否 |
| E | 否 | 是 | 是 |
| F | 否 | 是 | 否 |
| G | 否 | 否 | 是 |
| H | 否 | 否 | 否 |
类型 G#
这个类型最简单,不同的小球,不同的盒子,类似下面这个问题:
有 个人, 个景点,每个人只能去 1 个景点,求不同方案数.
由于每个人有 种选择方式且人与人之间互不干扰,所以总共方案数为
类型 D#
考虑这样一个场景,有 个人在分蛋糕,这个蛋糕是一个长方形的,长 个单位,宽 1 个单位,且每个人都要有蛋糕,且长宽必须是整数.
我们考虑竖着切,将其切成 份,就是要在 个空隙里选择 空隙,切一刀,所以,一共有 种方案.
类型 C#
相对于类型 D 来说,类型 C 允许有人没分到蛋糕,因此,我们先让每个人拿一个单位的蛋糕,所以一共就有 个单位,这样就转换为类型 D 了,答案是
类型 E#
考虑这样一个情况,有 个不同颜色的糖,需要分成 堆.
这个问题可以用 dp 解决.
状态定义:定义 dp[t][k] 表示在前 t 个糖中分成 k 堆的方案数.
初始状态:
状态转移方程
假定有一颗新的糖,你有两种选择,一种是新开一堆,另一种是将其放到之前的任意一堆之中.
这两种贡献中,其一是新开一堆这种方式的贡献,则之前的状态可以表示为 ,其二是放到之前的堆中这种方式的贡献,则之前的状态可以表示为 ,由于有 堆,所以这种方式一共有 种情况.
综合一下,可得:
最终状态
由于允许空盒,所以答案就是 ,也就是:
类型 F#
类似类型 E,但是由于不允许为空,所以最终状态是
类型 H#
类似类型 F,但是,对于 F 中的每一种情况,由于盒子是不同的,都会有组合相同但顺序不同的 种变体,所以答案是
类型 A#
已知有 个一模一样的乒乓球,将其装进 个一样的盒子里,有几种分法呢?由于每个盒子都是一样的,我们不妨规定前面盒子里球的数量必须比后面盒子里的多.
状态定义:定义 dp[t][k] 表示对于前 个小球,放 个盒子的方案数.
初始状态:
状态转移方程
假定最后一个盒子是空盒,那么可以把那个空盒去掉:
假定最后一个盒子不为空,那么由于最后一个盒子的球的数量小于前面的任何一个盒子,所以,在这种情况下,没有任何一个盒子为空,所以,我们不妨将每个盒子都取出来一个:
综合一下:
最终状态
由于允许空盒,所以答案就是 ,也就是:
不难发现,和类型 E 的最终状态是一样的.
类型 B#
类似类型 A,但是由于不允许为空,所以最终状态是