皇后游戏

GitHub跳转原题关系图返回列表

把奖金递推看成两机流水作业,使用 Johnson 法则分组排序后线性模拟。

OJ: luogu

题目 ID: P2123

难度:普及+/提高

标签:贪心排序调度

日期: 2026-06-22 20:43

题意

n 位大臣,每人有两个数 a_i,b_i。排好顺序后:

text
c_1 = a_1 + b_1
c_2 = max(a_1 + b_1, a_1 + a_2) + b_2 = max(a_1 + b_1 + b_2, a_1 + a_2 + b_2)
c_3 = max(c_2, a_1 + a_2 + a_3) + b_3 = max(a_1 + b_1 + b_2, a_1 + a_2 + b_2, a_1 + a_2 + a_3) + b_3 = max(a_1 + b_1 + b_2 + b_3, a_1 + a_2 + b_2 + b_3, a_1 + a_2 + a_3 + b_3)
c_i = max(c_{i-1}, a_1 + ... + a_i) + b_i

要求重新排序,使获得奖金最多的大臣奖金尽量小。

因为 c_i 不会下降,最大值就是最后的 c_n

思路

先看一个可以直接验证想法的朴素解:

cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 10;

struct Person {
    long long left_num;
    long long right_num;
};

int n;
Person people[MAXN];
int order_id[MAXN];

long long calc_order() {
    long long prefix_left = 0;
    long long last_reward = 0;
    for (int i = 0; i < n; i++) {
        int id = order_id[i];
        prefix_left += people[id].left_num;
        last_reward = max(last_reward, prefix_left) + people[id].right_num;
    }
    return last_reward;
}

void solve_case() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> people[i].left_num >> people[i].right_num;
        order_id[i] = i;
    }

    long long ans = -1;
    do {
        long long now = calc_order();
        if (ans == -1 || now < ans) {
            ans = now;
        }
    } while (next_permutation(order_id, order_id + n));

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

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

    int t;
    cin >> t;
    while (t--) {
        solve_case();
    }

    return 0;
}

暴力会枚举所有排列,计算每个排列的 c_n,取最小。这个做法只能用于小数据。

观察递推式:

text
c_i = max(c_{i-1}, prefix_a) + b_i

它和两台机器流水作业完全一样:

  • 第一阶段耗时是 a_i
  • 第二阶段耗时是 b_i
  • 第二阶段开始前,必须等当前任务第一阶段完成,也必须等上一个任务第二阶段完成。

所以可以使用 Johnson 法则:

  1. a_i <= b_i,放在前半段,并按 a_i 升序;
  2. a_i > b_i,放在后半段,并按 b_i 降序。

排好后从前到后模拟即可。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 20005;

struct Person {
    long long left_num;
    long long right_num;
};

int n;
Person people[MAXN];

bool cmp_person(const Person &x, const Person &y) {
    bool x_front = x.left_num <= x.right_num;
    bool y_front = y.left_num <= y.right_num;
    if (x_front != y_front) {
        return x_front > y_front;
    }
    if (x_front) {
        return x.left_num < y.left_num;
    }
    return x.right_num > y.right_num;
}

void solve_case() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> people[i].left_num >> people[i].right_num;
    }

    sort(people + 1, people + n + 1, cmp_person);

    long long prefix_left = 0;
    long long last_reward = 0;
    for (int i = 1; i <= n; i++) {
        prefix_left += people[i].left_num;
        last_reward = max(last_reward, prefix_left) + people[i].right_num;
    }

    cout << last_reward << '\n';
}

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

    int t;
    cin >> t;
    while (t--) {
        solve_case();
    }

    return 0;
}

复杂度

排序复杂度为:

text
O(n log n)

模拟为 O(n)O(n),空间复杂度为 O(n)O(n)

总结

本题看起来像国王游戏,但排序规则不同。

递推式本质是两机流水作业,直接套 Johnson 法则:前半段按 a 升序,后半段按 b 降序。排序后线性模拟最后的奖金即可。

新的完美解析