题解:洛谷 P14922 学习小组
【题目来源】
洛谷:P14922 [GESP202512 七级] 学习小组 - 洛谷
【题目描述】
班主任计划将班级里的 \(n\) 名同学划分为若干个学习小组,每名同学都需要分入某一个学习小组中。班级里的同学依次以 \(1,2,\ldots,n\) 编号,第 \(i\) 名同学有其发言积极度 \(c_i\)。
观察发现,如果一个学习小组中恰好包含编号为 \(p_1,p_2,\ldots,p_k\) 的 \(k\) 名同学,则该学习小组的基础讨论积极度为 \(a_k\),综合讨论积极度为 \(a_k+\max\{c_{p_1},c_{p_2},\ldots,c_{p_k}\}−\min\{c_{p_1},c_{p_2},\ldots,c_{p_k}\}\),也即基础讨论积极度加上小组内同学的最大发言积极度与最小发言积极度之差。
给定基础讨论积极度 \(a_1,a_2,\ldots,a_n\),请你计算将这 \(n\) 名同学划分为学习小组的所有可能方案中,综合讨论积极度之和的最大值。
【输入】
第一行,一个正整数 \(n\),表示班级人数。
第二行,\(n\) 个非负整数 \(c_1,c_2,\ldots,c_n\),表示每位同学的发言积极度。
第三行,\(n\) 个非负整数 \(a_1,a_2,\ldots,a_n\),表示不同人数学习小组的基础讨论积极度。
【输出】
输出一行,一个整数,表示所有划分方案中,学习小组综合讨论积极度之和的最大值。
【输入样例】
4
2 1 3 2
1 5 6 3
【输出样例】
12
【核心思想】
-
问题分析:有 \(n\) 名同学,每人有发言积极度 \(c_i\),需要将他们划分为若干个学习小组。对于大小为 \(k\) 的小组,其综合讨论积极度为 \(a_k + \max(c) - \min(c)\),其中 \(a_k\) 是给定基础值。求所有划分方案中,所有小组综合讨论积极度之和的最大值。本质是一个分组优化问题,由于极差 \(\max-\min\) 只与组内最大最小值有关,将 \(c\) 排序后,最优分组中每个小组的同学在排序数组中一定是连续的区间,否则可调整使极差不增。因此问题转化为:将排序后的数组 \(c\) 划分为若干连续段,每段长度为 \(k\) 的收益为 \(a_k + c_{r} - c_{l}\),求最大总收益。
-
算法选择:
- 区间 DP(一维):将 \(c\) 排序后,定义 \(dp[i]\) 表示考虑前 \(i\) 个同学(排序后)能获得的最大总收益。转移时枚举最后一段的起始位置 \(j\),则 \(dp[i] = \max_{1 \le j \le i} \{ dp[j-1] + a_{i-j+1} + c_i - c_j \}\)。其中 \(a_{len}\) 是长度为 \(len\) 的小组基础值。最终答案为 \(dp[n]\)。
- 复杂度:\(O(n^2)\),\(n \le 300\),完全可行。
-
关键步骤:
- 读入与排序:读入 \(n\)、\(c[1..n]\)、\(a[1..n]\),将 \(c\) 数组升序排列。
- DP 初始化:\(dp[0] = 0\)。
- 转移:外层 \(i\) 从 \(1\) 到 \(n\),内层 \(j\) 从 \(1\) 到 \(i\),计算长度 \(len = i - j + 1\),更新 \(dp[i] = \max(dp[i], dp[j-1] + a[len] + c[i] - c[j])\)。
- 输出:\(dp[n]\)。
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\)。
- 空间复杂度:\(O(n)\)。
-
区间 DP 的核心思想:
- 排序后连续性:因为极差仅取决于组内最大最小值,任意最优分组均可调整为组内同学在排序后连续,否则交换可改善或保持收益。
- 状态压缩:只需记录前缀 \(i\) 的最大收益,转移时枚举最后一段的起点。
- 收益计算:一段 \([j, i]\) 的收益为 \(a_{i-j+1} + c_i - c_j\),其中 \(c_i\) 和 \(c_j\) 分别为排序后的最大值和最小值。
- 适用场景:适用于分组后收益只与组内极差和大小相关的划分优化问题。
【算法标签】
普及+ #区间DP
【代码详解】
// 55分版本
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int n, c[N], a[N], dp[N]; // n: 目标长度,c: 输入数组但未使用,a: 长度为i的区间的价值,dp: 动态规划数组
int main()
{
cin >> n; // 输入目标长度n
for (int i = 1; i <= n; i++)
cin >> c[i]; // 输入数组c,但在后面的代码中没有使用
for (int i = 1; i <= n; i++)
cin >> a[i]; // 输入长度为i的区间的价值a[i]
// 完全背包问题:用各种长度的区间组合成长度为n,求最大总价值
for (int i = 1; i <= n; i++) // 遍历每种区间长度i
for (int j = i; j <= n; j++) // 遍历所有可能的长度j
dp[j] = max(dp[j], dp[j - i] + a[i]); // 状态转移方程
cout << dp[n]; // 输出组合成长度为n的最大价值
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int n, c[N], a[N], dp[N][N]; // n: 总长度,c: 额外收益数组,a: 区间价值数组,dp: 动态规划数组
int main()
{
cin >> n; // 输入总长度n
for (int i = 1; i <= n; i++)
cin >> c[i]; // 输入额外收益数组c
for (int i = 1; i <= n; i++)
cin >> a[i]; // 输入长度为i的区间的价值a[i]
sort(c + 1, c + n + 1); // 对c数组进行排序,用于计算额外收益
// 初始化:不选择任何区间(i=0)的情况
for (int i = 1; i <= n; i++)
dp[i][0] = dp[i - 1][0] + a[1]; // 全部用长度为1的区间填充,累积价值
// 动态规划
for (int i = 2; i <= n; i++) // i: 当前区间长度
for (int j = i; j <= n; j++) // j: 当前总长度
for (int k = 1; k <= j / 2; k++) // k: 选择的区间数量
{
// 状态转移:从总长度为j-i,已选k-1个区间转移过来
// 加上当前长度为i的区间价值a[i]
// 再加上额外收益:最大的k个c值减去最小的k个c值
dp[j][k] = max(dp[j][k], dp[j - i][k - 1] + a[i] + (c[n - k + 1] - c[k]));
}
int ans = -1e9; // 初始化答案为负无穷
for (int i = 0; i <= n; i++) // 遍历所有可能的区间数量
ans = max(ans, dp[n][i]); // 更新最大价值
cout << ans << endl; // 输出结果
return 0;
}
【运行结果】
4
2 1 3 2
1 5 6 3
12
浙公网安备 33010602011771号