AGC056D 题解

· · 题解

\text{statement}

一块黑板上写着 n 个整数。第 i 个整数记作 a_i。保证 n 是偶数。此外,给定 L,R

Alice 和 Bob 在玩一个游戏。他们轮流操作,Alice 先手。在每一轮中,玩家需要选择一个写在黑板上的数,并擦掉它。

游戏会在 n 轮后结束。令 s 为 Alice 擦掉的数之和。若 L \le s \le R,则 Alice 胜利,反之 Bob 胜利。你需要输出当两方都采取最优策略情况下的赢家。

## $\text{solution}

S = \sum_{1\le i\le n} a_iS_A 为 Alice 擦掉的数之和,S_B 为 Bob 擦掉的数之和。
记一个长为 m 序列 b_i 的邻项差分 \text{diff }b = (b_2 - b_1) + (b_4 - b_3) + \cdots + (b_m - b_{m-1})

首先转化题意。条件 L \le S_A \le R 可以通过整体乘 2S 转化为 2L - S \le S_A - S_B \le 2R - S。随后设 x = S - (L + R),整体加 x 后变为 L - R \le x + S_A - S_B \le R - L,即 |x + S_A - S_B| \le R - L

因此有转化后的问题:
给定整数 x。两人轮流操作,每次选择一个没有选过的数字 a_i。在 Alice 的回合,置 x=x + a_i,而在 Bob 的回合置 x=x - a_i。最终的分数即为 x 的绝对值。Alice 的目标是最小化这个值,而 Bob 的目标是最大化它。求当两方都采取最优策略情况下的赢家。

不妨假设 a_i 升序排列。我们断言,最终的分数可以通过如下方式求得:

容易发现 p 所有对答案有贡献的取值为 p = a_i。对于 p 的某一确定取值可以在 O(n) 的时间复杂度内计算答案,取其中最小值作为最终分数即可。
因此有总时间复杂度 O(n^2) 的算法。

证明:

可以通过如下方式计算分数:Alice 操作后,黑板上剩余 n-1 个整数。将目前可选的 n-1 个整数与目前的 x 一同进行升序排序,得到序列 b_i。Bob 操作后的分数即为 \text{diff }b

使用数学归纳法证明该方式的正确性。
n=2 时正确性显然。

假设已经证明了对 Bob 来说 n=k 的情况是成立的,现在需要证明对 Alice 来说 n=k + 1 的情况也是成立的。
Alice 的目标等价于将其中一个 a_i 更换为 a_i + x,使得 Bob 的分值最小。选择 a_i 对应着选择 p = a_i,这样的策略肯定能够产生可能得到的分数中最小的值,因此对 Alice 来说 n=k + 1 的情况通过该方式计算是正确的。

假设已经证明了对 Alice 来说 n=k + 1 的情况是成立的,现在需要证明对 Bob 来说 n=k+2 的情况也是成立的。
我们将 Bob 可选的 n-1 个整数与目前的 x 一同进行升序排序,得到序列 b_i。令 b_s = x。如果 Bob 在本轮中选择了 b_t(s \neq t),那么 Alice 可以选择 p=b_t 来得到分数 \text{diff }b。因此该值是分数的一个上界。
这个上界是可以取到的。不妨假设 s 是奇数。如果 Bob 在 s=1 时选择 t=n,在其余时刻选择 t = s-1,最终的分数就是该值。当 s 为偶数的时候类似:当 s=n 时选择 t=1,在其余时刻选择 t =s+1 即可。

根据数学归纳法,原断言成立。

总时间复杂度 O(n^2)

\text{code : }
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
int n, l, r, a[5005], s;
vector<int> ans;

signed main() {
    cin >> n >> l >> r; 
    rep(i,1,n) cin >> a[i], s += a[i]; s -= l + r;
    sort(a + 1, a + 1 + n);
    int ret = LLONG_MAX, now;
    rep(i,1,n) {
        ans.clear(); now = 0;
        rep(j,1,n) if (i != j) ans.emplace_back(a[j]);
        ans.insert(lower_bound(ans.begin(), ans.end(), a[i] + s), a[i] + s);
        for (int i = 0; i < ans.size(); i += 2) now += ans[i + 1] - ans[i];
        ret = min(ret, now);
    } puts(abs(ret) <= r - l ? "Alice" : "Bob");
}