记录163
#include<bits/stdc++.h> // 引入万能头文件,包含所有常用的标准库
using namespace std; // 使用标准命名空间
int t,m,n; // 定义全局变量t(测试数据组数)、m(苹果数)、n(盘子数)
int ans; // 定义全局变量ans,用来记录当前测试数据的合法方案总数
// remain_apple: 当前还剩下多少个苹果没放
// remain_plate: 当前还剩下多少个盘子没放
// min_apple: 当前这个盘子至少要放多少个苹果(保证非递减,避免重复)
void dfs(int remain_apple,int remain_plate,int min_apple){
// 如果只剩下最后1个盘子,剩下的苹果全部放进去,这算作一种合法方案
if(remain_plate==1){
ans++; // 方案数加1
return; // 结束当前递归分支
}
// 枚举当前盘子放多少个苹果(从min_apple开始枚举)
// 剪枝:因为后面还有remain_plate-1个盘子,且每个至少放i个
// 所以当前最多只能放 remain_apple / remain_plate 个
for(int i=min_apple;i<=remain_apple/remain_plate;i++){
dfs(remain_apple-i,remain_plate-1,i); // 递归搜索下一个盘子,传入减去i后的剩余苹果,盘子数减1,最小可选值更新为i
}
}
int main(){ // 主函数入口
ios::sync_with_stdio(false); // 关闭cin与stdio的同步,加快输入输出速度
cin.tie(0); // 解除cin与cout的绑定,进一步加快IO效率
cin>>t; // 输入测试数据的组数t
while(t--){ // 循环t次,处理每一组测试数据
cin>>m>>n; // 输入当前组的苹果数m和盘子数n
ans=0; // 每次测试数据开始前,将方案数清零
dfs(m,n,0); // 调用dfs开始搜索,初始剩余m个苹果,需放n个盘子,最小从0开始选(允许空盘)
cout<<ans<<"\n"; // 输出当前测试数据的合法方案总数
}
return 0; // 主函数正常结束,返回0
}
题目传送门
https://www.luogu.com.cn/problem/P2386
前言
我是一名专注信奥赛(GESP、CSP-J/S)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的优快云账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是一道非常经典的组合数学与深度优先搜索(DFS)剪枝问题。
-
问题转化(非递减序列模型):
题目要求将 mm 个相同的苹果放入 n 个相同的盘子,且允许空盘。因为盘子是相同的,所以 (5,1,1) 和 (1,1,5) 被视为同一种方案。为了避免重复计数,我们可以强制规定每个盘子放的苹果数量呈非递减顺序(即前一个盘子放的苹果数 ≤≤ 后一个盘子放的苹果数)。这样,每一种合法的分配方案都唯一对应一个非递减序列。 -
算法设计(DFS 与剪枝优化):
我们可以使用 DFS 逐个盘子进行分配。在搜索过程中,需要维护三个状态:剩余苹果数、剩余盘子数、以及当前盘子至少需要放的苹果数(即上一个盘子放的苹果数,保证非递减)。- 边界条件:当剩余盘子数为 1 时,说明前面的盘子都已经分配完毕,剩下的苹果必须全部放入最后一个盘子。由于是非递减序列,只要前面的分配合法,最后一步必然合法,直接方案数加 1 并返回。
- 枚举与剪枝:对于当前盘子,枚举放入的苹果数 i 。下界是
min_apple,上界则是通过平均值剪枝得出的:为了保证剩下的盘子也能满足非递减条件,当前盘子最多只能放remain_apple / remain_plate个苹果。这极大地减少了搜索树的规模。
代码分块详细解释
1. 全局变量与函数签名定义
#include<bits/stdc++.h>
using namespace std;
int t, m, n; // 定义全局变量t(测试数据组数)、m(苹果数)、n(盘子数)
int ans; // 定义全局变量ans,用来记录当前测试数据的合法方案总数
// remain_apple: 当前还剩下多少个苹果没放
// remain_plate: 当前还剩下多少个盘子没放
// min_apple: 当前这个盘子至少要放多少个苹果(保证非递减,避免重复)
void dfs(int remain_apple, int remain_plate, int min_apple){
- 详细分析:定义了三个全局变量用于主循环控制。
dfs函数是核心搜索函数,参数设计非常精妙:min_apple参数完美地解决了“盘子相同导致方案重复”的问题,它充当了当前枚举的下界,强制后续的分配不会小于之前的分配。
2. 核心逻辑:边界处理与剪枝枚举
// 如果只剩下最后1个盘子,剩下的苹果全部放进去,这算作一种合法方案
if(remain_plate == 1){
ans++; // 方案数加1
return; // 结束当前递归分支
}
// 枚举当前盘子放多少个苹果(从min_apple开始枚举)
// 剪枝:因为后面还有remain_plate-1个盘子,且每个至少放i个
// 所以当前最多只能放 remain_apple / remain_plate 个
for(int i = min_apple; i <= remain_apple / remain_plate; i++){
dfs(remain_apple - i, remain_plate - 1, i); // 递归搜索下一个盘子
}
}
- 详细分析:
- 边界处理:当
remain_plate == 1时,意味着只剩下一个盘子,此时无论剩下多少苹果,都只能全放进去。由于我们一直在维护非递减序列,只要前面的分配合法,最后一步必然合法,因此直接ans++并返回。 - 循环与剪枝:
for循环的下界是min_apple,保证了非递减;上界是remain_apple / remain_plate,这是极其关键的平均值剪枝。假设还剩 10 个苹果和 4 个盘子,当前盘子最多只能放 10/4=210/4=2 个,因为如果放 3 个,剩下的 7 个苹果分给 3 个盘子,平均值大于 2,必然违反非递减规则。这个剪枝将时间复杂度大幅降低。
- 边界处理:当
3. 主函数:多组数据测试与状态重置
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin >> t;
while(t--){
cin >> m >> n;
ans = 0; // 每次测试数据开始前,将方案数清零
dfs(m, n, 0); // 调用dfs开始搜索,初始剩余m个苹果,需放n个盘子,最小从0开始选(允许空盘)
cout << ans << "\n";
}
return 0;
}
- 详细分析:主函数处理多组测试数据。每次调用
dfs前,必须将全局变量ans重置为 0。初始调用时,min_apple传入 0,完美契合了题目中“允许有的盘子空着不放”的条件。
核心逻辑总结
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 非递减约束 | min_apple 参数传递 | 记录上一个盘子放的苹果数,作为当前枚举下界 | 完美解决了“盘子相同导致 (5,1,1) 和 (1,1,5) 重复计数”的痛点 |
| 边界快速返回 | if(remain_plate == 1) | 仅剩一个盘子时直接累加方案数 | 避免了无意义的深层递归,提升了搜索效率 |
| 平均值剪枝 | i <= remain_apple / remain_plate | 限制当前盘子能放苹果的最大值 | 极大地缩减了搜索树的规模,防止超时 |
| 状态重置 | ans = 0 | 每组测试数据开始前清零计数器 | 保证多组测试数据之间的独立性,防止答案污染 |
| 允许空盘 | 初始调用 dfs(m, n, 0) | 将初始最小苹果数设为 0 | 满足了题目中“允许有的盘子空着不放”的特殊要求 |
2489



被折叠的 条评论
为什么被折叠?



