题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 颗宝石,每颗有颜色 \(C_i\) 和价值 \(V_i\)。需选出 \(K\) 颗,且至少包含 \(M\) 种不同颜色,求最大价值总和。这是一个贪心 + 分类选取问题,核心在于先保证颜色多样性约束,再最大化总价值。

  2. 算法选择

    • 按价值降序排序 + 两轮贪心:第一轮优先选取不同颜色的高价值宝石以满足 \(M\) 种颜色约束,第二轮从剩余宝石中选取价值最高的补足 \(K\)
  3. 关键步骤

    • 读入数据\(N\)\(K\)\(M\),以及每颗宝石的 \((C_i, V_i)\)
    • 按价值降序排序\(a[1..N]\)\(V_i\) 从大到小排列
    • 第一轮——颜色覆盖\(i\)\(1\)\(N\)):
      • 若已选够 \(M\) 种颜色,终止
      • 若当前宝石颜色未被选过:标记颜色、标记选中、累加价值、计数 \(+1\)
    • 第二轮——价值补足\(i\)\(1\)\(N\)):
      • 若已选够 \(K\) 颗,终止
      • 若当前宝石未被选中:标记选中、累加价值、计数 \(+1\)
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序主导复杂度,两轮遍历各 \(O(N)\)
    • 空间复杂度:\(O(N)\),存储宝石信息和颜色标记数组
  5. 贪心策略正确性分析

    • 颜色优先原则:第一轮优先保证 \(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
posted @ 2026-08-26 10:12  团爸讲算法  阅读(10)  评论(0)    收藏  举报