整数划分相关
整数划分的核心思想
1. 问题本质
把整数 \(m\) 分成 \(n\) 个数,顺序无关。比如把 5 分成 2 个数:
- \(1+4\)、\(2+3\) 算两种不同方案
- 但 \(1+4\) 和 \(4+1\) 算同一种方案
2. 如何避免重复计数?
既然顺序无关,我们就强制规定一个顺序,比如非递减:
这样 \(1+4\) 合法,\(4+1\) 非法,从根本上杜绝了重复。
3. 两个“基本操作”
假设我们已经有了一个合法的非递减序列,怎么“生成”更多的序列?反过来,怎么“拆解”一个序列?
操作 A:在序列最前面加一个 1
[3, 5] -> [1, 3, 5]
这对应“原序列的第一项是 1”的情况。
操作 B:给所有数都加一个常数 k
[3, 5] -> [5, 7] (k=2)
这对应“原序列的第一项 > 1”的情况(如果第一项是 1,没法全体加 k 还保持非递减的“第一项是 1”这个特征,因为 \(1+k \ne 1\))。
关键点:这两个操作是互逆的,而且能覆盖所有情况。任何非递减序列,要么第一项是 1(用操作 A 拆),要么第一项 > 1(用操作 B 拆)。
这里补充一下什么是“可重复”,“可重复”说的是划分结果中,同一个数字可以出现多次,也可以理解为“每个数字的使用次数不限”。
我们看下面的例子:
| 划分目标 | 可重复 | 不可重复 |
|---|---|---|
| \(6\) 分成 \(3\) 个数 | \(1+1+4\),\(1+2+3\),\(2+2+2\)(\(3\) 种) | \(1+2+3\)(\(1\) 种) |
| \(8\) 分成 \(2\) 个数 | \(1+7\),\(2+6\),\(3+5\),\(4+4\)(\(4\) 种) | \(1+7\),\(2+6\),\(3+5\)(\(3\) 种,\(4+4\) 不合法) |
六种情况
情况 1.1:可重复,无上限
问题:把 \(m\) 分成 \(n\) 个正整数,可重复。
状态:\(f[i][j]\) = 把 \(j\) 分成 \(i\) 个正整数的方案数(非递减)。
推导:
对一个合法序列 \(a_1 \le a_2 \le \dots \le a_i\),和为 \(j\):
我们有两种操作方式:
- 在一个合法序列前面插入一个 \(1\),序列长度变为 \(i+1\),总和变成 \(j+1\),所以 \(f[i+1][j+1]+=f[i][j]\),也就是 \(f[i][j]+=f[i-1][j-1]\)。
- 将一个合法序列里的每一项加上 \(1\),序列长度还是 \(i\),总和变成 \(j+i\),所以 \(f[i][j+i]+=f[i][j]\),也就是 \(f[i][j]+=f[i][j-i]\)。
递推式:
边界:\(f[0][0] = 1\)。
情况 1.2:不可重复,无上限
问题:把 \(j\) 分成 \(i\) 个互不相同的正整数。
状态:\(f[i][j]\) 依然是非递减序列(但由于不可重复,实际上是严格递增 \(a_1 < a_2 < \dots < a_i\))。
-
整体加 1:
\(f[i][j+i] += f[i][j]\) (也就是 \(f[i][j] += f[i][j-i]\))。 -
整体加 1,再在开头添一个 1:
不难发现,这是依然可以保持严格递增的。\(f[i+1][j+i+1] += f[i][j]\),反推得 \(f[i][j] += f[i-1][j-i]\)。
递推式:
情况 2.1:可重复,全奇数
问题:把 \(j\) 分成 \(i\) 个奇数,可重复。
状态:\(f[i][j]\) 是非递减的奇数序列(如 \([1, 1, 3, 5]\))。
推导:
如果手里是奇数,怎么保持奇数?
-
在前面加一个 1:
所以:\(f[i+1][j+1] += f[i][j]\),也就是 \(f[i][j] += f[i-1][j-1]\)。 -
整体加 2:
所以:\(f[i][j+2i] += f[i][j]\),也就是 \(f[i][j] += f[i][j-2i]\)。
递推式:
情况 2.2:不可重复,全奇数 (重点看这里,印证你的理解)
问题:把 \(j\) 分成 \(i\) 个互不相同的奇数。
状态:\(f[i][j]\) 是严格递增的奇数序列(如 \([1, 3, 7]\))。
推导:
手里是严格递增的奇数序列,怎么长大?
-
整体加 2:
所以:\(f[i][j+2i] += f[i][j]\),也就是 \(f[i][j] += f[i][j-2i]\)。 -
整体加 2,再在开头添一个 1:
所以:\(f[i+1][j+2i+1] += f[i][j]\),也就是 \(f[i][j] += f[i-1][j-2i+1]\)。
递推式:
3.1:可重复,每个数 < \(x\)
推导:
从 1.1 的“加操作”出发
1.1 的递推式:
对应的两种“生成”操作:
| 操作 | 效果 | 对状态的影响 |
|---|---|---|
| 前面加 1 | 序列长度 +1,总和 +1 | \(f[i-1][j-1] \rightarrow f[i][j]\) |
| 全体加 1 | 长度不变,总和 +i | \(f[i][j-i] \rightarrow f[i][j]\) |
现在加上限制:每个数都 \(< x\)。
我们需要分别检查这两种操作会不会产生非法序列。
- 第一种操作:前面加 1
从合法序列 \(f[i-1][j-1]\) 出发,在最前面插入一个 1:
会不会产生非法?
- 新加入的数是 1,而 \(1 < x\) 恒成立
- 剩下的 \(i-1\) 个数本来就是合法的(都 \(< x\))
所以这条路径永远安全,不需要任何减法。
第一项仍然是:
- 第二种操作:全体加 1
从合法序列 \(f[i][j-i]\) 出发,对所有数都加 1:
什么时候会出问题?
- 合法状态下,每个数都 \(< x\),即最大只能是 \(x-1\)
- 全体加 1 之后,如果某个数原本是 \(x-1\),加完就变成了 \(x\)
- 而 \(x\) 是不允许的(要求 \(< x\))
所以问题出在:\(f[i][j-i]\) 里包含了 \(x-1\) 的方案。
关键:从 \(f[i][j-i]\) 中剔除含 \(x-1\) 的方案
假设 \(f[i][j-i]\) 中有一个合法序列:
其中恰好某个位置上的数是 \(x-1\)。
我们把这个 \(x-1\) 从序列中删掉:
- 剩下的数有 \(i-1\) 个
- 因为原序列是非递减的,删掉一个数后仍然非递减
- 所有剩下的数仍然都 \(< x\)(因为它们本来就都 \(\le x-1\),删掉的只是其中一个 \(x-1\),剩下的依然 \(\le x-1\))
剩下的序列的和是多少?
原来的和是 \(j-i\),删掉的数是 \(x-1\),所以:
因此,含 \(x-1\) 的方案数和以下状态一一对应:
正确的递推式
把三种情况拼起来:
3.2:不可重复,每个数 < \(x\)
推导:
从 1.2(不可重复,无上限)的递推式出发:
这里两个分支都可能产生非法序列。非法序列长什么样?
- 因为所有数都要 \(< x\),非法序列只有一个可能:其中恰好有一个数等于 \(x\)。
- 为什么不可能有更多问题?因为基础递推只能生成从合法状态通过“全体 +1”得到的序列,而合法状态中最大值为 \(x-1\),加 1 后最大值为 \(x\),不会超过 \(x\)。
- 又因为“不可重复”,序列中不可能出现两个 \(x\)(出现了就重复了)。
所以,非法序列的结构必然是:
恰好有一个数等于 \(x\),其余 \(i-1\) 个数都 \(< x\) 且互不相同。
关键一步:去掉那个非法的 \(x\)
把非法序列中的那个 \(x\) 删掉,剩下的 \(i-1\) 个数:
- 互不相同
- 每个数都 \(< x\)
- 总和为 \(j - x\)
这恰好就是一个合法的 \(f[i-1][j-x]\) 的划分!
反过来,任意一个 \(f[i-1][j-x]\) 的合法划分,在末尾(或正确的位置)添上一个 \(x\),就得到一个非法序列。
因此:
非法方案数 = \(f[i-1][j-x]\)
所以递推式减去它即可:
本文来自博客园,作者:MistyPost,转载请注明原文链接。

浙公网安备 33010602011771号