跳转至

I - Soldier Game

基本信息

题目出处2018 ICPC 亚洲区域赛青岛站
队伍通过率6/373 (1.6%)

题解

我们换一种方式描述题目:

用长度为 \(1\)\(2\) 的线段覆盖整个序列,线段的价值就是线段覆盖的元素之和。最小化最大价值和最小价值之差。

考虑用线段树解决该问题。维护 \(f(l, r, x \in \{0, 1\}, y \in \{0, 1\})\) 表示覆盖子数组 \(a_l, a_{l + 1}, \cdots, a_r\) 的最大价值最小是多少。其中:

  • \(x = 0\) 表示覆盖 \(a_l\) 的线段被完全包含在区间 \([l, r]\) 中,即覆盖 \(a_l\) 的线段可能是 \([l, l]\)\([l, l + 1]\)
  • \(x = 1\) 表示覆盖 \(a_l\) 的线段有一部分不在 \([l, r]\) 中,即覆盖 \(a_l\) 的线段是 \([l - 1, l]\)
  • \(y = 0\) 表示覆盖 \(a_r\) 的线段被完全包含在区间 \([l, r]\) 中,即覆盖 \(a_r\) 的线段可能是 \([r, r]\)\([r - 1, r]\)
  • \(y = 1\) 表示覆盖 \(a_r\) 的线段有一部分不在 \([l, r]\) 中,即覆盖 \(a_r\) 的线段是 \([r, r + 1]\)

可以得到线段树上的递归方程:

\[ f(l, r, x, y) = \min_{k \in {0, 1}} \{ \max(f(l, m, x, k), f(m + 1, r, k, y)) \} \]

其中 \(m = \lfloor\frac{l + r}{2}\rfloor\)。答案就是 \(f(1, n, 0, 0)\),初值为:

  • \(f(i, i, 0, 0) = a_i\),这是长度为 \(1\) 的线段。
  • \(f(i, i, 0, 1) = a_i + a_{i + 1}\),这是长度为 \(2\) 的线段。
  • \(f(i, i, 1, 0) = -\infty\),此时覆盖 \(a_i\) 的代价算在前一个区间里。
  • \(f(i, i, 1, 1) = +\infty\)\(a_i\) 不能同时被两个线段覆盖。

接下来考虑枚举线段的最小价值 \(v\),并将所有价值小于 \(v\) 的线段移除(不需要真的移除,可以把它们的价值改为 \(+\infty\)),此时答案就是 \(f(1, n, 0, 0) - v\)

一共只有 \((2n - 1)\)\(v\) 值,每次用线段树修改线段的价值并维护 \(f\) 的值即可。时间复杂度 \(\mathcal{O}(n\log n)\)

参考代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <bits/stdc++.h>
#define MAXN ((int) 1e5)
#define INF ((long long) 1e18)
using namespace std;
typedef pair<int, int> pii;
typedef pair<long long, pii> plii;

int n, A[MAXN + 10];
long long ans;

long long f[MAXN * 4 + 10][2][2];

// 套递归方程,更新线段树上的 f 值
void update(int id) {
    int nxt = id << 1;
    for (int i = 0; i <= 1; i++) for (int j = 0; j <= 1; j++) {
        f[id][i][j] = INF;
        for (int k = 0; k <= 1; k++) f[id][i][j] = min(f[id][i][j], max(f[nxt][i][k], f[nxt | 1][k][j]));
    }
}

// 构建线段树
void build(int id, int l, int r) {
    if (l == r) {
        // 递归方程初值
        f[id][0][0] = A[l];
        f[id][0][1] = l < n ? A[l] + A[l + 1] : INF;
        f[id][1][0] = l > 1 ? -INF : INF;
        f[id][1][1] = INF;
    } else {
        int nxt = id << 1, mid = (l + r) >> 1;
        build(nxt, l, mid);
        build(nxt | 1, mid + 1, r);
        update(id);
    }
}

// 把以 pos 为起点,len 为长度的线段的价值改成 +INF
void modify(int id, int l, int r, int pos, int len) {
    if (l == r) {
        if (len == 1) f[id][0][0] = INF;
        else f[id][0][1] = INF;
    } else {
        int nxt = id << 1, mid = (l + r) >> 1;
        if (pos <= mid) modify(nxt, l, mid, pos, len);
        else modify(nxt | 1, mid + 1, r, pos, len);
        update(id);
    }
}

void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &A[i]);

    build(1, 1, n);
    // 将所有线段的价值排序,以便枚举最小价值
    vector<plii> vec;
    for (int i = 1; i <= n; i++) vec.push_back(plii(A[i], pii(i, 1)));
    for (int i = 1; i < n; i++) vec.push_back(plii(A[i] + A[i + 1], pii(i, 2)));
    sort(vec.begin(), vec.end());

    ans = INF;
    // 枚举最小价值
    for (plii p : vec) {
        ans = min(ans, f[1][0][0] - p.first);
        modify(1, 1, n, p.second.first, p.second.second);
    }
    printf("%lld\n", ans);
}

int main() {
    int tcase; scanf("%d", &tcase);
    while (tcase--) solve();
    return 0;
}