题解:P12458 [JOI2025 预选赛 R2] 冰淇淋

· · 题解

此题 143 个测试点?恐怖如斯……

题意简述

Alice 和 Bob 在定制冰淇淋,规则如下:

  1. Alice 先选择一种口味(价格数组 A)。
  2. Bob 接着选择一种蛋筒(价格数组 B)。
  3. Alice 最后选择一种配料(价格数组 C)。

冰淇淋总价格 S = A_i + B_j + C_k,得分定义为 |S - P|。Alice 的目标是最大化得分,Bob 的目标是最小化得分。双方均采取最优策略,计算最终得分。

思路

一个典型的博弈论题目。

游戏分为三个阶段,采用逆向分析可得:

  1. 首先,当已知 A_iB_j,Alice 会选择 C_k 最大化 |A_i + B_j + C_k - P|
  2. 然后,当已知 A_i,Bob 会选择 B_j 最小化 Alice 在第 3 步能获得的最大得分。
  3. 最后,Alice 会选择 A_i 最大化 Bob 在第二步中最小化的得分。

形式化地,定义:

此时我们注意到,函数 f(s)s = A_i + B_j) 在 s 的不同区间有不同行为:

  1. s \leq P - Ef(s) = P - s - D(递减)。
  2. P - E < s < P - Df(s) = \max(P - s - D, s + E - P)(先减后增,最小值在 s_0 = P - \frac{D+E}{2})。
  3. s \geq P - Df(s) = s + E - P(递增)。

综上,本题策略为:

对于每个 A_i

  1. 计算关键点:

  2. 在排序后的 B 数组中搜索候选 B_j

    • 区间 \mathrm{I}:取 \leq key_1 的最大 B_j
    • 区间 \mathrm{III}:取 \geq key_2 的最小 B_j
    • 区间 \mathrm{II}:取最接近 s_0 的两个 B_j(左侧最大和右侧最小且在 (key_1, key_2) 内)。
  3. 计算每个候选 B_j 对应的 f(s),取最小值作为 g(A_i)

  4. 答案取所有 g(A_i) 的最大值。

美汁汁(\text{zhīr})的代码时间~

#include <bits/stdc++.h>
#define Code using
#define by namespace
#define CleverSea std
Code by CleverSea;

const int N = 2e5 + 10;

long long A[N], B[N], C[N];

int main() {
    long long X, Y, Z, P;
    scanf("%lld %lld %lld %lld", &X, &Y, &Z, &P);
    for (int i = 0; i < X; i++) {
        scanf("%lld", &A[i]);
    }
    for (int i = 0; i < Y; i++) {
        scanf("%lld", &B[i]);
    }
    for (int i = 0; i < Z; i++) {
        scanf("%lld", &C[i]);
    }
    sort(B, B + Y);
    // 计算配料的最小值D和最大值E
    long long minC = C[0], maxC = C[0];
    for (int i = 1; i < Z; i++) {
        if (C[i] < minC) minC = C[i];
        if (C[i] > maxC) maxC = C[i];
    }
    long long ans = 0;
    // 遍历每种口味
    for (int i = 0; i < X; i++) {
        long long key1 = P - maxC - A[i]; // 区间I上界
        long long key2 = P - minC - A[i]; // 区间III下界
        vector<long long> cf; // 存储候选得分
        // 候选1:区间I中最大的Bj(<=key1)
        int pos1 = upper_bound(B, B + Y, key1) - B;
        if (pos1 > 0) {
            long long Bj = B[pos1 - 1];
            long long s = A[i] + Bj;
            long long fv1 = llabs(s + minC - P);
            long long fv2 = llabs(s + maxC - P);
            long long fv = max(fv1, fv2);
            cf.push_back(fv);
        }
        // 候选2:区间III中最小的Bj(>=key2)
        int pos3 = lower_bound(B, B + Y, key2) - B;
        if (pos3 < Y) {
            long long Bj = B[pos3];
            long long s = A[i] + Bj;
            long long fv1 = llabs(s + minC - P);
            long long fv2 = llabs(s + maxC - P);
            long long fv = max(fv1, fv2);
            cf.push_back(fv);
        }
        // 计算区间II的理想点s0
        double s0v = (2.0 * P - minC - maxC) / 2 - A[i];
        // 候选3:区间II中小于等于s0v的最大Bj
        long long flov = (long long)floor(s0v);
        int posl = upper_bound(B, B + Y, flov) - B;
        if (posl > 0) {
            long long can = B[posl - 1];
            if (can > key1 && can < key2) { // 在开区间内
                long long s = A[i] + can;
                long long fv = max(llabs(s + minC - P), llabs(s + maxC - P));
                cf.push_back(fv);
            }
        }
        // 候选4:区间II中大于等于s0v的最小Bj
        long long ceiv = (long long)ceil(s0v);
        int posh = lower_bound(B, B + Y, ceiv) - B;
        if (posh < Y) {
            long long can = B[posh];
            if (can > key1 && can < key2) { // 在开区间内
                long long s = A[i] + can;
                long long fv = max(llabs(s + minC - P), llabs(s + maxC - P));
                cf.push_back(fv);
            }
        }
        // 确保候选列表非空(理论上不会为空,但还是检查一下)
        if (!cf.empty()) {
            long long minv = *min_element(cf.begin(), cf.end());
            if (minv > ans) {
                ans = minv;
            }
        }
    }
    printf("%lld\n", ans);
    return 0;
}

其中排序 B 数组 O(Y \log Y),遍历 A 数组,每个元素在 B 上二分查找 O(\log Y)X 个元素 O(X \log Y),总复杂度 O((X + Y) \log Y)