P14361 [CSP-S 2025] 社团招新

反悔贪心:先让每个人去最满意部门,再把超额部门中损失最小的人换出去。

课堂版 先建模 再看代码

一、先回答问题

问题本质

在 3 个部门中最大化总满意度,同时每个部门人数不能超过 n/2。

容易走偏

一开始就试图同时平衡三个部门,容易写成复杂匹配;本题只会有一个部门超额。

二、核心观察

把题目变成模型
建模先得到不受限制的最优分配,再只修正唯一可能超额的部门。
优化超额部门内按换出损失排序,取最小的 need 个。
正确性所有人起点都是个人最优;若必须换出若干人,选择损失最小者不会比选择损失更大者差。

三、互动演示

--
--
--

四、自己选答案

问题 1

为什么最多只有一个部门会超额?

问题 2

如果某人从超额部门换走,应换到哪里?

五、手推结果

步骤要看什么结论
1每个人的最大满意度先求没有限制时的最大总和
2三个部门人数若都不超过 n/2,答案已经最优
3超额部门每人的换出损失损失越小,越适合被换出去
4需要换出的人数cnt[d] - n/2

六、C++14 参考代码

可提交代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;

        vector<array<int, 3>> a(n);
        for (int i = 0; i < n; i++) {
            cin >> a[i][0] >> a[i][1] >> a[i][2];
        }

        long long ans = 0;
        vector<int> belong(n);
        int cnt[3] = {0, 0, 0};

        for (int i = 0; i < n; i++) {
            int best = 0;
            for (int j = 1; j < 3; j++) {
                if (a[i][j] > a[i][best]) best = j;
            }
            belong[i] = best;
            cnt[best]++;
            ans += a[i][best];
        }

        int limit = n / 2;
        for (int d = 0; d < 3; d++) {
            if (cnt[d] <= limit) continue;

            vector<int> loss;
            for (int i = 0; i < n; i++) {
                if (belong[i] != d) continue;
                int otherBest = 0;
                for (int j = 0; j < 3; j++) {
                    if (j != d) otherBest = max(otherBest, a[i][j]);
                }
                loss.push_back(a[i][d] - otherBest);
            }

            sort(loss.begin(), loss.end());
            int need = cnt[d] - limit;
            for (int i = 0; i < need; i++) ans -= loss[i];
            break;
        }

        cout << ans << '\n';
    }

    return 0;
}
`belong[i]` 记录初始最优部门。
`loss` 中只放超额部门的人。
答案用 `long long` 保存。