Description
Finn 正在玩一个叫做 Twos and Threes 的游戏。Twos and Threes 是一个单人游戏,它在一维棋盘上进行。在初始位置,有 N 个方块排成一行,每个方块标有 A 或 B。方块从左到右编号为 1 到 N。Finn 可以进行以下操作:
- 选择 2 或 3 个连续的方块,它们有相同的标签,将它们从棋盘上移除并将剩余的方块连接起来,从左到右重新编号方块。
如果棋盘上的所有方块都被移除,Finn 就赢得了游戏。你的任务是帮助 Finn 确定一个赢得游戏的操作序列,或者确定游戏无法获胜。
1≤N≤106。
Solution
容易发现每次选择 2 或 3 个删掉,等价于可以删掉任意长度 ≥2 的相同连续段,所以可以先把所有极长连续段缩起来,设长度为 a1,a2,…,am。
显然一次操作一定是把一整个连续段删掉,否则不优。假设我们把第 i 个连续段删掉,整个序列会变成 a1,a2,…,ai−2,ai−1+ai+1,ai+2,…,am。由于我们只关心操作连续段的长度是否大于等于 2,所以把 ai 变为 [ai≥2] 后,一次操作就变为:[...?1?...]→[...1...],因为两个段合并后长度一定大于等于 2。
先考虑 m=2k+1 的情况。
经过打表可以发现有解的充要条件是满足 ak+1=1 或者不存在一个长度为 k 的 0 连续段。
证明就考虑如果 ak+1=1 时,每次删掉正中间的段即可。否则如果存在一个长度为 k 的 0 连续段的话,设 pre 为 k+1 左边第一个 1,nxt 为 k+1 右边第一个 1。尽管 [1,pre] 和 [nxt,m] 全是 1,总共能消掉的 0 的数量也为 pre−1+m−nxt=m−1−(nxt−pre)≤k−1,所以一定不可能。(注:减一是因为操作开头或者末尾的 1 没有意义)
如果不存在长度为 k 的 0 连续段,则我们一定可以通过一直删掉 nxt 位置的连续段来把 pre 移到正中间,然后一直操作 pre 即可。
如果 m=2k,则最后一次操作一定是对 11 进行操作,由于在之前一定没有边界上的操作,所以两个 1 一定对应两个长度为奇数的区间,枚举一下是否存在这样的划分即可。
时间复杂度: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>
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); for (int i = nxt, j = nxt, c = 1; c <= mid - pre; --i, ++j, ++c) { 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]); 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); } } 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; while (T--) dickdreamer(); return 0; }
|