P10055 [CCO 2022] Good Game 题解

Description

Finn 正在玩一个叫做 Twos and Threes 的游戏。Twos and Threes 是一个单人游戏,它在一维棋盘上进行。在初始位置,有 NN 个方块排成一行,每个方块标有 A\tt{A}B\tt{B}。方块从左到右编号为 11NN。Finn 可以进行以下操作:

  • 选择 2233 个连续的方块,它们有相同的标签,将它们从棋盘上移除并将剩余的方块连接起来,从左到右重新编号方块。

如果棋盘上的所有方块都被移除,Finn 就赢得了游戏。你的任务是帮助 Finn 确定一个赢得游戏的操作序列,或者确定游戏无法获胜。

1N1061 \leq N \leq 10^6

Solution

容易发现每次选择 2233 个删掉,等价于可以删掉任意长度 2\geq 2 的相同连续段,所以可以先把所有极长连续段缩起来,设长度为 a1,a2,,ama_1,a_2,\ldots,a_m

显然一次操作一定是把一整个连续段删掉,否则不优。假设我们把第 ii 个连续段删掉,整个序列会变成 a1,a2,,ai2,ai1+ai+1,ai+2,,ama_1,a_2,\ldots,a_{i-2},a_{i-1}+a_{i+1},a_{i+2},\ldots,a_m。由于我们只关心操作连续段的长度是否大于等于 22,所以把 aia_i 变为 [ai2][a_i\geq 2] 后,一次操作就变为:[...?1?...][...1...][...?1?...]\to [...1...],因为两个段合并后长度一定大于等于 22

先考虑 m=2k+1m=2k+1 的情况。

经过打表可以发现有解的充要条件是满足 ak+1=1a_{k+1}=1 或者不存在一个长度为 kk00 连续段。

证明就考虑如果 ak+1=1a_{k+1}=1 时,每次删掉正中间的段即可。否则如果存在一个长度为 kk00 连续段的话,设 preprek+1k+1 左边第一个 11nxtnxtk+1k+1 右边第一个 11。尽管 [1,pre][1,pre][nxt,m][nxt,m] 全是 11,总共能消掉的 00 的数量也为 pre1+mnxt=m1(nxtpre)k1pre-1+m-nxt=m-1-(nxt-pre)\leq k-1,所以一定不可能。(注:减一是因为操作开头或者末尾的 11 没有意义)

如果不存在长度为 kk00 连续段,则我们一定可以通过一直删掉 nxtnxt 位置的连续段来把 prepre 移到正中间,然后一直操作 prepre 即可。

如果 m=2km=2k,则最后一次操作一定是对 1111 进行操作,由于在之前一定没有边界上的操作,所以两个 11 一定对应两个长度为奇数的区间,枚举一下是否存在这样的划分即可。

时间复杂度:O(n)O(n)

Code

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e6 + 5;

int n, m;
int a[kMaxN], pos[kMaxN], pre[kMaxN], nxt[kMaxN];
std::string str;
std::vector<std::pair<int, int>> vec;

void work(int x, int v) { assert(v == 2 || v == 3); vec.emplace_back(x, v); }

bool check(int l, int r) {
if ((r - l) & 1) return 0;
int mid = (l + r) >> 1;
return a[mid] >= 2 || nxt[mid] - pre[mid] - 1 < (r - l) / 2;
}

void upd(int pos, int len) {
assert(len >= 2);
if (len & 1) work(pos, 3), len -= 3;
for (; len;) work(pos, 2), len -= 2;
}

void solve(int l, int r) {
static bool del[kMaxN] = {0};
int mid = (l + r) >> 1, pre = ::pre[mid], nxt = ::nxt[mid];
assert(pre >= l && nxt <= r && nxt - pre - 1 < (r - l) / 2);
// std::cerr << l << ' ' << r << '\n';
// for (int i = l; i <= r; ++i) std::cerr << a[i] << " \n"[i == r];
for (int i = nxt, j = nxt, c = 1; c <= mid - pre; --i, ++j, ++c) {
// std::cerr << "??? " << i << ' ' << j << ' ' << pos[i] << '\n';
upd(pos[i], a[i] + (i != j) * a[j]), del[i] = del[j] = 1;
if (c == mid - pre) a[i - 1] += a[j + 1], del[j + 1] = 1;
}
for (int i = pre, j = pre, c = 1; c <= pre - l + 1; --i, ++c) {
for (; del[j]; ++j) {}
assert(!del[i] && !del[j]);
// std::cerr << "heige " << i << ' ' << j << '\n';
upd(pos[i], a[i] + (i != j) * a[j]), del[j] = 1;
}
}

void print() {
std::cout << vec.size() << '\n';
for (auto [x, v] : vec) std::cout << x << ' ' << v << '\n';
}

void dickdreamer() {
std::cin >> n >> str; str = " " + str;
for (int i = 1, lst = 0; i <= n; ++i) {
if (i == n || str[i] != str[i + 1]) {
a[++m] = i - lst, lst = i;
pos[m] = pos[m - 1] + a[m - 1] + (m == 1);
// std::cerr << pos[m] << '\n';
}
}
for (int i = 1; i <= m; ++i) {
if (a[i] >= 2) pre[i] = i;
else pre[i] = pre[i - 1];
}
nxt[m + 1] = m + 1;
for (int i = m; i; --i) {
if (a[i] >= 2) nxt[i] = i;
else nxt[i] = nxt[i + 1];
}
if (m & 1) {
if (!check(1, m)) return void(std::cout << "-1\n");
solve(1, m), print();
} else {
for (int i = 1; i < m; i += 2) {
if (check(1, i) && check(i + 1, m)) {
solve(i + 1, m), solve(1, i), print();
return;
}
}
std::cout << "-1\n";
}
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P10055 [CCO 2022] Good Game 题解
https://sobaliuziao.github.io/2025/08/28/post/141acd8f.html
作者
Egg_laying_master
发布于
2025年8月28日
许可协议