题解:洛谷 AT_abc461_c Variety
【题目来源】
洛谷:AT_abc461_c [ABC461C] Variety - 洛谷
【题目描述】
有 \(N\) 颗宝石,第 \(i\) 颗宝石的颜色(用整数表示)为 \(C_i\),价值为 \(V_i\)。
请从这 \(N\) 颗宝石中选择 \(K\) 颗宝石,选出的宝石必须包含至少 \(M\) 种不同的颜色。
求所选宝石的价值总和的最大值。(保证在给定的输入中总可以做出这样的选择。)
【输入】
输入从标准输入读入,格式如下:
\(N\ K\ M\)
\(C_1\ V_1\)
\(C_2\ V_2\)
\(\vdots\)
\(C_N\ V_N\)
【输出】
输出所能取得的最大价值总和。
【输入样例】
5 3 2
1 30
1 40
1 50
2 10
3 20
【输出样例】
110
【核心思想】
-
问题分析:给定 \(N\) 颗宝石,每颗有颜色 \(C_i\) 和价值 \(V_i\)。需选出 \(K\) 颗,且至少包含 \(M\) 种不同颜色,求最大价值总和。这是一个贪心 + 分类选取问题,核心在于先保证颜色多样性约束,再最大化总价值。
-
算法选择:
- 按价值降序排序 + 两轮贪心:第一轮优先选取不同颜色的高价值宝石以满足 \(M\) 种颜色约束,第二轮从剩余宝石中选取价值最高的补足 \(K\) 颗
-
关键步骤:
- 读入数据:\(N\)、\(K\)、\(M\),以及每颗宝石的 \((C_i, V_i)\)
- 按价值降序排序:\(a[1..N]\) 按 \(V_i\) 从大到小排列
- 第一轮——颜色覆盖(\(i\) 从 \(1\) 到 \(N\)):
- 若已选够 \(M\) 种颜色,终止
- 若当前宝石颜色未被选过:标记颜色、标记选中、累加价值、计数 \(+1\)
- 第二轮——价值补足(\(i\) 从 \(1\) 到 \(N\)):
- 若已选够 \(K\) 颗,终止
- 若当前宝石未被选中:标记选中、累加价值、计数 \(+1\)
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),排序主导复杂度,两轮遍历各 \(O(N)\)
- 空间复杂度:\(O(N)\),存储宝石信息和颜色标记数组
-
贪心策略正确性分析:
- 颜色优先原则:第一轮优先保证 \(M\) 种不同颜色,每种颜色选该颜色中价值最高的宝石(因为已按全局价值排序,首次遇到某颜色即为该颜色最高价值)
- 价值最大化原则:第二轮在已满足颜色约束的基础上,从剩余宝石中选价值最高的 \(K-M\) 颗,全局降序排序确保这一步的最优性
- 无后效性保证:第一轮选中的宝石不参与第二轮,但第二轮从剩余中选全局最高价值,不会因为第一轮的选择而损失更优解——若某高价值宝石与第一轮某宝石同色,它会在第二轮被选中;若不同色,它已在第一轮被选中
- 约束满足性:两轮分别保证"至少 \(M\) 种颜色"和"恰好 \(K\) 颗",且总价值在约束下最大化
- 适用于带多样性约束的选择问题、分类贪心策略设计类问题
【算法标签】
普及- #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005; // 定义最大数量
int n, k, m, ans; // 物品数n,总选择数k,颜色数m,总价值ans
struct Node
{
int c, v; // 颜色c,价值v
bool flag; // 是否被选择
}a[N];
bool color[N]; // 颜色是否被使用
bool cmp(Node x, Node y) // 排序比较函数
{
return x.v > y.v; // 按价值从大到小排序
}
signed main() // 主函数
{
cin >> n >> k >> m; // 输入物品数、总选择数、颜色数
for (int i=1; i<=n; i++) // 读取所有物品
{
cin >> a[i].c >> a[i].v; // 输入颜色和价值
a[i].flag = false; // 初始化未选择
}
sort(a+1, a+n+1, cmp); // 按价值排序
int cnt = 0; // 已选择物品计数
for (int i=1; i<=n; i++) // 第一轮:每个颜色至少选一个
{
if (cnt==m) break; // 已选够m个颜色
if (!color[a[i].c]) // 如果这个颜色还没被选过
{
color[a[i].c] = true; // 标记颜色被使用
a[i].flag = true; // 标记物品被选择
ans += a[i].v; // 累加价值
cnt++; // 计数增加
}
else // 如果颜色已被选过
continue; // 跳过
}
for (int i=1; i<=n; i++) // 第二轮:选满k个物品
{
if (cnt==k) break; // 已选够k个物品
if (!a[i].flag) // 如果物品还未被选择
{
a[i].flag = true; // 标记物品被选择
ans += a[i].v; // 累加价值
cnt++; // 计数增加
}
else // 如果物品已被选择
continue; // 跳过
}
cout << ans << endl; // 输出总价值
return 0; // 程序正常结束
}
【运行结果】
5 3 2
1 30
1 40
1 50
2 10
3 20
110
浙公网安备 33010602011771号