P13015 [GESP202506 六级] 学习小组

记录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代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

 核心解题思路

这道题表面上是一个复杂的排列组合与分组问题,但如果我们深入分析其数学本质,就会发现它完美契合了算法中的“完全背包模型”

  1. 模型转化

    • 背包容量:班级总人数 nn 就是背包的总容量。
    • 物品:我们可以组建的 1 人小组、22 人小组、……、 n人小组,就是不同的“物品”。
    • 物品重量:组建一个 k 人小组,需要消耗 kk 个人,因此 k 就是物品的重量。
    • 物品价值:组建一个 k 人小组,能获得的讨论积极度是 a​ ,这就是物品的价值。
    • 完全背包特性:因为题目没有限制某种人数的小组只能组建一次(例如 4 个人完全可以分成两个 2 人小组),所以每种“物品”可以被无限次选取,这正是完全背包问题的核心特征。
  2. 状态定义与转移
    我们定义 dp[i] 表示将 i 名同学划分成若干个小组时,能获得的最大讨论积极度之和。
    为了求出 dp[i],我们可以逆向思考最后一步:假设最后一个小组有 j 个人(1≤j≤i ),那么剩下的 i - j 个人已经构成了最优划分,其最大积极度为 dp[i - j]。加上这最后 j 人小组带来的积极度 a[j],我们就得到了一个候选方案。我们只需要枚举所有可能的 j,取其中的最大值即可。

  3. 算法优势
    如果采用暴力搜索(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])完全背包核心递推公式将复杂的分组排列组合问题,优雅地转化为线性动态规划求解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值