记录128
#include<bits/stdc++.h>
using namespace std;
const int N=1010;//定义常量N,表示班级人数的最大范围(根据题目数据范围设定)
int n;//定义全局变量n,表示班级的总人数
int a[N];//定义数组a,a[i]表示i人小组的讨论积极度
int dp[N];//定义dp数组,dp[i]表示i名同学分组能获得的最大讨论积极度之和
int main(){//完全背包
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;//读入班级总人数n
for(int i=1;i<=n;i++){//循环读入不同人数小组的讨论积极度
cin>>a[i];//读入i人小组的讨论积极度
}
for(int i=1;i<=n;i++){//外层循环:遍历总人数从1到n(背包容量)
for(int j=1;j<=i;j++){//内层循环:枚举最后一个小组的人数j(物品重量)
dp[i]=max(dp[i],dp[i-j]+a[j]);//状态转移:取当前最大值与拆出j人小组后的最大值中的较大者
}
}
cout<<dp[n];//输出n名同学分组能获得的最大讨论积极度之和
return 0;//程序结束
}
题目传送门
https://www.luogu.com.cn/problem/P13015
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题表面上是一个复杂的排列组合与分组问题,但如果我们深入分析其数学本质,就会发现它完美契合了算法中的“完全背包模型”。
-
模型转化:
- 背包容量:班级总人数 nn 就是背包的总容量。
- 物品:我们可以组建的 1 人小组、22 人小组、……、 n人小组,就是不同的“物品”。
- 物品重量:组建一个 k 人小组,需要消耗 kk 个人,因此 k 就是物品的重量。
- 物品价值:组建一个 k 人小组,能获得的讨论积极度是 a ,这就是物品的价值。
- 完全背包特性:因为题目没有限制某种人数的小组只能组建一次(例如 4 个人完全可以分成两个 2 人小组),所以每种“物品”可以被无限次选取,这正是完全背包问题的核心特征。
-
状态定义与转移:
我们定义dp[i]表示将i名同学划分成若干个小组时,能获得的最大讨论积极度之和。
为了求出dp[i],我们可以逆向思考最后一步:假设最后一个小组有j个人(1≤j≤i ),那么剩下的i - j个人已经构成了最优划分,其最大积极度为dp[i - j]。加上这最后j人小组带来的积极度a[j],我们就得到了一个候选方案。我们只需要枚举所有可能的j,取其中的最大值即可。 -
算法优势:
如果采用暴力搜索(DFS),时间复杂度是指数级的,在 n≤1000 的数据规模下必然超时。而采用完全背包的动态规划解法,时间复杂度仅为 O(N^2),能够轻松通过所有测试点。
代码分块详细解释
1. 头文件、常量定义与全局变量
#include<bits/stdc++.h>
using namespace std;
const int N=1010; // 定义常量 N,表示班级人数的最大范围(根据题目数据范围 n ≤ 1000 设定,留有余量防越界)
int n; // 定义全局变量 n,表示班级的总人数
int a[N]; // 定义数组 a,a[i] 表示 i 人小组的讨论积极度(即完全背包中物品的价值)
int dp[N]; // 定义 dp 数组,dp[i] 表示 i 名同学分组能获得的最大讨论积极度之和(即背包容量为 i 时的最大价值)
- 详细分析:这部分完成了基础数据结构的搭建。将数组定义为全局变量是一个好习惯,因为全局变量存储在静态存储区,默认初始化为 0,且不会像局部变量那样占用有限的栈空间,从而避免了在 N=1000N=1000 时可能出现的栈溢出风险。
dp数组的默认初始值 0 也恰好符合题意:0 个人分组时的积极度为 0,这为后续的状态转移提供了完美的边界条件。
2. 输入与 IO 优化
int main(){ // 主函数入口
ios::sync_with_stdio(false);
cin.tie(0); // 关闭 C++ 标准输入输出流与 C 标准输入输出的同步,并解除 cin 与 cout 的绑定
cin>>n; // 读入班级总人数 n
for(int i=1;i<=n;i++){ // 循环读入不同人数小组的讨论积极度
cin>>a[i]; // 读入 i 人小组的讨论积极度,存入数组 a
}
- 详细分析:
ios::sync_with_stdio(false);和cin.tie(0);是 C++ 竞赛编程中的标准 IO 优化模板。当数据量达到 10001000 级别且涉及大量循环输入输出时,默认的cin/cout可能会因为底层同步机制导致超时。这两行代码能大幅提升读写效率。随后的for循环将题目给定的积极度数组读入,为接下来的动态规划准备好“物品价值”数据。
3. 核心逻辑:完全背包动态规划
for(int i=1;i<=n;i++){ // 外层循环:遍历总人数从 1 到 n(相当于遍历背包容量)
for(int j=1;j<=i;j++){ // 内层循环:枚举最后一个小组的人数 j(相当于枚举物品的重量)
dp[i]=max(dp[i],dp[i-j]+a[j]); // 状态转移:取当前最大值与拆出 j 人小组后的最大值中的较大者
}
}
- 详细分析:这是整个算法的灵魂所在。
- 外层循环
i代表当前正在计算多少个人的最优分组方案。 - 内层循环
j代表我们尝试在当前的i个人中,切分出最后j个人组成一个独立的小组。 - 状态转移方程
dp[i] = max(dp[i], dp[i-j] + a[j])的含义是:对于i个人,我们有多种切分最后一步的方法(最后 1 人一组、最后 2 人一组……直到最后i人一组)。dp[i-j]是前i-j个人的历史最优解,加上当前这个j人小组的收益a[j],就构成了一个完整的候选解。通过max函数不断比较,dp[i]最终会收敛到所有切分方案中的全局最大值。 - 这种正序枚举的方式天然满足了完全背包中“物品可以无限次选取”的特性,因为我们在计算
dp[i]时,dp[i-j]可能已经包含过a[j]的贡献了。
- 外层循环
4. 输出结果
cout<<dp[n]; // 输出 n 名同学分组能获得的最大讨论积极度之和
return 0; // 程序正常结束
}
- 详细分析:经过前面的双重循环,
dp数组已经自底向上地计算出了所有子问题的最优解。dp[n]自然就代表了将全部n名同学进行最优划分后的最大总积极度。直接输出该值即可。
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 全局定义 | int dp[N] | 定义状态数组并隐式初始化 | 避免栈溢出,同时提供 dp[0]=0 的完美边界条件 |
| IO 优化 | ios::sync_with_stdio(false) | 提升输入输出效率 | 防止在 N=1000N=1000 规模下因频繁 IO 导致程序超时 |
| 背包容量遍历 | for(int i=1;i<=n;i++) | 自底向上枚举总人数 | 确保在计算 dp[i] 时,所有规模小于 i 的子问题均已解决 |
| 物品重量枚举 | for(int j=1;j<=i;j++) | 枚举最后一步的小组人数 | 穷举所有可能的切分方式,保证不漏掉任何最优解 |
| 状态转移 | dp[i] = max(dp[i], dp[i-j]+a[j]) | 完全背包核心递推公式 | 将复杂的分组排列组合问题,优雅地转化为线性动态规划求解 |

1081

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



