Description
有一座桥,上面有 2N 个传送门。这 2N 个传送门分成 N 组,每组恰好两个。我们用 1 到 N 的数字来区分每组传送门。
你从最左边进入这座桥,沿着朝右的方向一直走,每次碰到一个传送门,你会传送到它对应的传送门的位置,然后继续往右,直到走到第 2N 个传送门右边为止。可以证明在这个问题中,我们一定能走到第 2N 个传送门右边。
比如 6 个门,依次为 1,3,2,1,2,3,用 1 到 6 表示这些门的标号。那么你行走的路径为 1↪4→5↪3→4↪1→2↪6。其中 ↪ 表示传送,→ 表示行走。
现在你忘了传送门的具体对应关系,只记得对于每一段 i→i+1 你是否经过。问能不能是否存在一种合法的传送门的方案,如果存在的话,构造一组解。
N≤105。
Solution
首先设 ai 表示 (i−1)→i 是否经过,那么先把 a1 和 a2n+1 设为 1,即可以把走的过程看成一个环。
对于每个传送门,如果其左右的路都是 1,则将其看成 M,只有左边是 1 看成 S,只有右边是 1 看成 T。
那么没有标记的传送门一定不会经过,这些门可以随便填。其余的一定会经过。
注意到 M 门需要向右经过一次和传送一次,S 门只能向右,T 只能传送经过,所以 M 只能和 M 自己匹配,而 S 只能和 T 匹配。
所以如果 M 的个数为偶数,则一定无解。
考虑没有 S 和 T 的情况。
如果 M 的个数是 4 的倍数,可以如下构造:[1,2,1,2,3,4,3,4,…,2n−1,2n,2n−1,2n]。
如果模 4 余 2 则一定无解。
现在考虑上 S 和 T。
首先如果 M 的个数是 4 的倍数,按照上面的方式把所有 M 的位置拿出来匹配,对于相邻的 ST 匹配在一起,经过手玩仍然是对的。具体可以看这个图(从这里拿的,侵删)

如果 M 的个数模 4 余 2,上面就不能直接做了。但是注意到上面的做法很强,在中间随便加入一些东西仍然是对的,所以考虑选择两个 M 进行匹配,并达到等效删除的作用。
考虑下面这种情况:

将相邻的 M 极长段拿出来,如果前面的段被两个 ST 包围,则可以分别将两段第一个的 M 匹配,同时第一个 ST 的 T 和第二个 ST 的 S 也进行匹配。
其余的不变,手玩可发现这么做对于其余的结构相当于是在其中嵌入一部分数。要想这东西大概是注意到前面将相邻 ST 匹配,如果不匹配一个 TS 就一定不会走出回头路把这两个数删掉。现在先作出一个 TS,然后显然要跨段,可以猜到是将两段分别第一个 M 进行匹配。
还有一种情况是这个:

和上面对称,容易证明如果找不到这样的两个段就一定不合法,因为这样的状态就一定长成 MMMSTMMM 的形式,还是通过手玩可以知道其不合法。
对于剩下没有匹配的位置,直接把这些位置拿出来做 M 模 4 为 0 的做法即可。
时间复杂度: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
| #include <bits/stdc++.h>
const int kMaxN = 2e5 + 5;
int n, m, cnt; int a[kMaxN], op[kMaxN], id[kMaxN], res[kMaxN]; std::string s;
void add(int x, int y) { assert(!res[x] && !res[y]); res[x] = res[y] = ++cnt; }
void dickdreamer() { std::cin >> n >> s; m = cnt = 0; a[1] = a[2 * n + 1] = 1; for (int i = 1; i <= 2 * n; ++i) res[i] = 0; for (int i = 2; i <= 2 * n; ++i) a[i] = s[i - 2] - '0'; int cntM = 0; op[0] = op[2 * n + 1] = 0; for (int i = 1; i <= 2 * n; ++i) { if (!a[i] && a[i + 1]) op[i] = 1; else if (a[i] && !a[i + 1]) op[i] = 2; else if (a[i] && a[i + 1]) op[i] = 3, ++cntM; else op[i] = 0; } if (cntM & 1) return void(std::cout << "No\n"); if (cntM % 4 == 2) { int lstl = 0, lstr = 0; bool fl = 0; for (int l = 1, r = 1; l <= 2 * n; l = r + 1) { for (; l <= 2 * n && op[l] != 3; ++l) {} if (l > 2 * n) break; r = l; for (; r < 2 * n && op[r + 1] == 3; ++r) {} if (lstl && lstr) { if (op[l - 1] == 1 && op[r + 1] == 2) { add(lstr, r), add(l - 1, r + 1), fl = 1; } else if (op[lstl - 1] == 1 && op[lstr + 1] == 2) { add(lstl, l), add(lstl - 1, lstr + 1), fl = 1; } } lstl = l, lstr = r; if (fl) break; } if (!fl) return void(std::cout << "No\n"); } for (int i = 1; i <= 2 * n; ++i) if (!res[i] && op[i] == 3) id[++m] = i; assert(m % 4 == 0); for (int i = 1; i <= m; i += 4) add(id[i], id[i + 2]), add(id[i + 1], id[i + 3]); int lst[3] = {0}; for (int i = 1; i <= 2 * n; ++i) { if (!res[i]) { if (op[i] == 0) { if (lst[0]) add(lst[0], i), lst[0] = 0; else lst[0] = i; } else if (op[i] == 1) { if (lst[2]) add(lst[2], i), lst[2] = 0; else lst[1] = i; } else { assert(op[i] == 2); if (lst[1]) add(lst[1], i), lst[1] = 0; else lst[2] = i; } } } std::cout << "Yes\n"; for (int i = 1; i <= 2 * n; ++i) std::cout << res[i] << " \n"[i == 2 * 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 cid, T = 1; std::cin >> T >> cid; while (T--) dickdreamer(); return 0; }
|