在组合数学的世界里,有一类问题堪称 “高频刚需”—— 将相同元素进行分配。比如:把 10 个相同的苹果分给 3 个小朋友,每个小朋友至少分 1 个,有多少种分法?再比如:求解不定方程x1+x2+x3=8的正整数解有多少组? 这些看似毫无关联的问题,背后都隐藏着同一个解题神器 ——隔板法。它就像组合计数中的 “瑞士军刀”,能快速将复杂的分配问题转化为简单的组合数计算,让你在算法竞赛中秒解同类题目。 本文将从隔板法的核心原理入手,详细拆解两种基础模型、推导过程和适用场景,再通过洛谷经典真题(方程的解)实战演练,提供完整的 C++ 代码(含空间优化版本)和细节注释。无论你是算法新手,还是想巩固组合计数的进阶选手,跟着这篇文章学,就能彻底掌握隔板法的精髓!下面就让我们正式开始吧!
隔板法的核心思想是:将 “相同元素的分配” 转化为 “在元素间隙中插入隔板” 的组合问题。
举个直观的例子:有 6 个相同的小球,要分成 4 组(对应 4 个不同的盒子),每个组至少有 1 个小球。怎么分?
这个例子为大家诠释了隔板法的本质:相同元素的分配问题,等价于在元素间隙中选隔板的组合问题。
隔板法主要有两个核心模型,分别对应 “每个盒子至少 1 个元素” 和 “盒子可以为空” 两种情况,掌握这两个模型,就能解决 90% 以上的分配问题。
问题描述:有 n 个相同的元素,要分配到 k 个不同的盒子中,每个盒子至少放 1 个元素,求有多少种分配方案?
推导过程:

适用场景:

的正整数解组数(xi≥1)。
示例验证:

,和之前的例子一致。
问题描述:有 n 个相同的元素,要分配到 k 个不同的盒子中,盒子可以为空(允许不放元素),求有多少种分配方案?
推导过程:

。
适用场景:

的非负整数解组数(xi≥0)。
示例验证:

。
为了方便大家快速区分和记忆两个模型,这里整理了核心对比表:
模型 | 条件限制 | 方案数公式 | 对应方程解 |
|---|---|---|---|
模型一 | 每个盒子至少 1 个元素 | 正整数解(xi≥1) | |
模型二 | 盒子可以为空 | 非负整数解(xi≥0) |

记忆口诀:
题目链接:https://www.luogu.com.cn/problem/P1771

佳佳碰到了一个难题,需要解决不定方程a1+a2+...+ak=g(x),其中:
输入:一行两个正整数 k 和 x。
输出:方程的正整数解组数。
示例一:
输入:3 2
输出:3
解释:g(2)=22=4mod1000=4,方程变为a1+a2+a3=4,正整数解有 3 组:(1,1,2)、(1,2,1)、(2,1,1)。

,得到 n = g (x)。

的正整数解组数,正好对应隔板法的模型一(每个ai≥1)。

。

可能很大(比如

是一个超级大的数),需要用高精度计算。

,但高精度除法实现复杂。这里可以用杨辉三角的递推公式

,用加法递推避免除法,简化实现。

:用杨辉三角递推 + 高精度存储结果。
#include <iostream>
using namespace std;
typedef long long LL;
// 常量定义:
// N:n的最大可能值(x^x mod 1000 ≤ 999,所以n-1 ≤ 998)
// M:k的最大可能值(k-1 ≤ n-1 ≤ 998)
// K:高精度数组的位数(足够存储C(998,499),约1e300,所以150位足够)
const int N = 1010, M = 110, K = 150;
int k, x, n;
// f[i][j][k]:高精度存储C(i,j),第三维是数字的每一位(逆序存储,方便进位)
int f[N][M][K];
// 快速幂:计算a^b mod p,用于计算x^x mod 1000
LL qpow(LL a, LL b, LL p) {
a %= p; // 先取模,避免溢出
LL ret = 1;
while (b) {
if (b & 1) ret = ret * a % p; // 若b为奇数,乘上当前a
a = a * a % p; // a平方
b >>= 1; // b右移一位(等价于b//2)
}
return ret;
}
// 高精度加法:c = a + b(a和b是逆序存储的高精度数组)
void add(int c[], int a[], int b[]) {
for (int i = 0; i < K - 1; ++i) {
c[i] += a[i] + b[i]; // 对应位相加
c[i + 1] += c[i] / 10; // 进位
c[i] %= 10; // 保留当前位
}
}
int main() {
cin >> k >> x;
// 步骤1:计算n = x^x mod 1000
n = qpow(x, x, 1000);
// 步骤2:判断是否有解(n >= k才可能有正整数解)
if (n < k) {
cout << 0 << endl;
return 0;
}
// 步骤3:用杨辉三角递推计算C(n-1, k-1)
// 杨辉三角边界:C(i, 0) = 1(所有i)
for (int i = 0; i < n; ++i) {
f[i][0][0] = 1; // C(i,0) = 1,逆序存储,第0位是个位1
}
// 递推公式:C(i,j) = C(i-1,j) + C(i-1,j-1)
for (int i = 1; i < n; ++i) {
// j的范围:1 <= j <= min(i, k-1)(因为我们只需要计算到C(n-1, k-1))
for (int j = 1; j <= min(i, k-1); ++j) {
add(f[i][j], f[i-1][j], f[i-1][j-1]);
}
}
// 步骤4:输出结果(f[n-1][k-1]是逆序存储,需要倒序输出)
int p = K - 1;
// 找到最高位(跳过前面的0)
while (p >= 0 && f[n-1][k-1][p] == 0) --p;
// 倒序输出每一位
while (p >= 0) cout << f[n-1][k-1][p--];
cout << endl;
return 0;
}上面的代码用了三维数组 f [N][M][K],空间复杂度较高。由于递推公式

中,第 i 层只依赖第 i-1 层的数据,因此可以优化为二维数组,减少空间占用。
空间优化版代码:
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1010, M = 110, K = 150;
int k, x, n;
// 优化为二维数组:f[j][k]存储当前层的C(i,j)
int f[M][K];
LL qpow(LL a, LL b, LL p) {
a %= p;
LL ret = 1;
while (b) {
if (b & 1) ret = ret * a % p;
a = a * a % p;
b >>= 1;
}
return ret;
}
// 高精度加法:c += b(因为优化后,a就是c的上一轮值)
void add(int c[], int b[]) {
for (int i = 0; i < K - 1; ++i) {
c[i] += b[i];
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
int main() {
cin >> k >> x;
n = qpow(x, x, 1000);
if (n < k) {
cout << 0 << endl;
return 0;
}
// 初始化:C(0,0) = 1
f[0][0] = 1;
// 递推:i从1到n-1(因为要计算C(n-1, k-1))
for (int i = 1; i < n; ++i) {
// 注意:j要从min(i, k-1)倒序遍历,避免覆盖上一轮的f[j-1]
for (int j = min(i, k-1); j >= 1; --j) {
add(f[j], f[j-1]);
}
}
// 输出结果
int p = K - 1;
while (p >= 0 && f[k-1][p] == 0) --p;
while (p >= 0) cout << f[k-1][p--];
cout << endl;
return 0;
}add实现两个高精度数组的相加,处理进位时,当前位除以 10 的商是进位,余数是当前位的值。示例一输入:3 2
测试用例二:输入:2 5
测试用例三:输入:5 3
隔板法的基础模型看似简单,但通过灵活变形,可以解决很多复杂的组合计数问题。以下是几个常见的扩展场景:
问题描述:有 n 个相同元素,分给 k 个不同盒子,每个盒子至少 m 个元素(m≥2),求方案数。
解法:转化为模型一(每个盒子至少 1 个)。

(若 n' < k,则方案数为 0)。
示例:将 10 个相同小球分给 3 个盒子,每个盒子至少 2 个,方案数是多少?

。
问题描述:有 n 个相同元素,分给 k 个不同盒子,每个盒子最多放 t 个元素,求方案数。
解法:正难则反 + 容斥原理 + 隔板法。

。

,其中 max_i 是满足i∗(t+1)≤n的最大整数(超过则方案数为 0)。
示例:将 5 个相同小球分给 2 个盒子,每个盒子最多放 3 个,方案数是多少?

。

。

。
问题描述:有 n 个相同元素,分成 m 组,每组至少 1 个元素,再将这 m 组分配给 k 个不同盒子(k≥m),每个盒子最多分 1 组,求方案数。
解法:组合数 + 隔板法。

。

。

。
示例:将 6 个相同小球分成 2 组(每组至少 1 个),再分配给 3 个不同盒子(每个盒子最多 1 组),方案数是多少?
隔板法是组合计数中最实用的解题方法之一,核心是将 “相同元素的分配问题” 转化为 “插隔板的组合问题”。掌握两个基础模型(每个盒子至少 1 个、允许为空),就能解决大多数基础题;通过 “转化法”“正难则反”“容斥原理” 等技巧,还能应对复杂的扩展场景。 本文通过洛谷真题 P1771,详细演示了隔板法的实际应用,同时解决了高精度计算、空间优化等关键问题,提供了可直接运行的 C++ 代码。希望大家在学习后,能够多做练习,灵活运用隔板法,在算法竞赛中快速破解同类题目。 最后,记住隔板法的核心口诀:“相同元素分不同盒,隔板插空来相助;至少一个减一选,允许为空加一选”。相信只要勤加练习,你一定能把隔板法运用得炉火纯青!