【题目链接】
【思路要点】
- 将起点连向第一个触发器,每一个触发器连向同一个开关 r o o t root root 。
- 构造一棵 i i i 层的满二叉树,使得 2 i ≥ N 2^i≥N 2i≥N ,这样,如果 r o o t root root 被经过了 2 i 2^i 2i 次,那么所有节点都会回到 X X X 状态,是符合条件的。
- 我们将第 1 , 2 , . . . , 2 i − N 1,2,...,2^i-N 1,2,...,2i−N 次经过 r o o t root root 走到的叶子结点连向 r o o t root root ,将第 2 i − N + i 2^i-N+i 2i−N+i 次经过 r o o t root root 走到的叶子结点连向 a i a_i ai ,即可得到一个使用节点数不超过 2 N 2N 2N 的做法。
- 稍加思考,我们发现,若一个子树内所有的叶子结点均连向 r o o t root root ,那么我们可以将这个子树删除,将这个子树的根节点连向 r o o t root root 。于是,类似于线段树的区间定位,我们将靠左的 2 i − N 2^i-N 2i−N 个节点用这种方式连向 r o o t root root 即可。
- 需要特判 N = 1 N=1 N=1 的情况。
- 时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN) ,使用节点数不超过 N + L o g 2 N N+Log_{2}N N+Log2N。
【代码】
#include "doll.h" #include<bits/stdc++.h> const int MAXM = 1e5 + 5; const int MAXP = 4e5 + 5; using namespace std; vector <int> c, x, y; int n, m, p, root, waste; bool state[MAXP]; void build(int &pos, int tot) { pos = ++p; state[p] = false; if (tot == 2) return; int tmp = tot >> 1; if (tmp > waste) build(x[pos - 1], tot >> 1), x[pos - 1] *= -1; else { waste -= tmp; x[pos - 1] = -root; } build(y[pos - 1], tot >> 1), y[pos - 1] *= -1; } bool link(int pos, int tot, int dest) { state[pos] ^= true; if (tot == 2) { if (state[pos]) x[pos - 1] = dest; else y[pos - 1] = dest; return true; } if (state[pos]) { if (-x[pos - 1] == root) return false; else return link(-x[pos - 1], tot >> 1, dest); } else { if (-y[pos - 1] == root) return false; else return link(-y[pos - 1], tot >> 1, dest); } } void create_circuit(int tm, vector <int> list) { n = list.size(), m = tm; list.push_back(0); c.resize(m + 1), c[0] = list[0]; x.resize(n + log2(n)); y.resize(n + log2(n)); if (n == 1) { c[list[0]] = 0; answer(c, x, y); return; } int tot = 1; while (tot < n) tot <<= 1; waste = tot - n; build(root, tot); for (int i = 1; i <= m; i++) c[i] = -root; assert(waste <= 1); for (int i = 1; i <= waste; i++) while (!link(root, tot, -root)); for (int i = 1; i <= n; i++) while (!link(root, tot, list[i])); answer(c, x, y); }