BZOJ 3510 - 首都 「 $LCT$ 动态维护树的重心」
这题 FlashHu 的优化思路值得借鉴
前置引理
-
树中所有点到某个点的距离和中,到重心的距离和是最小的。
-
把两棵树通过某一点相连得到一颗新的树,新的树的重心必然在连接原来两棵树重心的路径上。
-
一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
-
一棵树最多有两个重心,且相邻;同时,拥有奇数个节点的树只有一个重心
- 其实是树的重心本身的定义:各个子树大小皆不超过总节点数的一半的节点即为树的重心(证明:不管向哪一侧移动,对应的子树节点个数都是 $\le$ 树的总节点一半的,也就是说,剩下的节点数 $\ge$ 总节点数的一半,故该点为中心)
题解
根据引理 $1$ ,题目很明显是要求维护树的重心
最简单的方法是启发式合并,根据引理 $3, 5$ ,每加一条边,若当前子树大小 $\ge$ 总节点数的一半,就将重心往这里移一次,复杂度 $O (n \log^2 n)$
对于优化,根据引理 $2$ ,取出两棵原树在新树上之间的路径,进行类似二分的操作,对于当前查询区间 $[L, p), (p, R]$ ,并同时存储 $L$ 之前的子树节点总和 $lsum$ ,以及 $R$ 之后的 $rsum$ ,且因为 $Splay$ 上是根据中序遍历,所以当前节点在 $Splay$ 上的左右子节点可以代表分割的左右区间,故若 $lsum + size[son[p][0]] \le half \&\& size[son[p][1]] + rsum \ge half$ ,那么点 $p$ 即为树的一个重心(注意由于在当前 $Splay$ 上 $lsum$ 所属的节点属于实点且已经跳过,故 $size[son[p][0]]$ 不会重复计算到 $lsum$,因为 $LCT$ 中维护的子树为 $Splay$ 中的子树,虚树的维护是一定不会将同一 $Splay$ 的实点加进去的),根据引理 $4$ ,若总节点数为奇数,那么已经可以结束查找了;反之则需继续查找是否有编号更小的,看左右区间哪边子节点多就去哪里就好了,总复杂度 $O (n \log n)$
代码
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 5 using namespace std; 6 7 const int MAXN = 1e05 + 10; 8 9 int father[MAXN]= {0}; 10 int son[MAXN][2]= {0}; 11 int subtree[MAXN]= {0}, virsize[MAXN]= {0}; 12 int revtag[MAXN]= {0}; 13 14 int isroot (int p) { 15 return son[father[p]][0] != p && son[father[p]][1] != p; 16 } 17 int sonbel (int p) { 18 return son[father[p]][1] == p; 19 } 20 void reverse (int p) { 21 if (! p) 22 return ; 23 swap (son[p][0], son[p][1]); 24 revtag[p] ^= 1; 25 } 26 void pushup (int p) { 27 subtree[p] = subtree[son[p][0]] + subtree[son[p][1]] + virsize[p] + 1; 28 } 29 void pushdown (int p) { 30 if (revtag[p]) { 31 reverse (son[p][0]), reverse (son[p][1]); 32 revtag[p] = 0; 33 } 34 } 35 void rotate (int p) { 36 int fa = father[p], anc = father[fa]; 37 int s = sonbel (p); 38 son[fa][s] = son[p][s ^ 1]; 39 if (son[fa][s]) 40 father[son[fa][s]] = fa; 41 if (! isroot (fa)) 42 son[anc][sonbel (fa)] = p; 43 father[p] = anc; 44 son[p][s ^ 1] = fa, father[fa] = p; 45 pushup (fa), pushup (p); 46 } 47 int Stack[MAXN]; 48 int top = 0; 49 void splay (int p) { 50 top = 0, Stack[++ top] = p; 51 for (int nd = p; ! isroot (nd); nd = father[nd]) 52 Stack[++ top] = father[nd]; 53 while (top > 0) 54 pushdown (Stack[top]), top --; 55 for (int fa = father[p]; ! isroot (p); rotate (p), fa = father[p]) 56 if (! isroot (fa)) 57 sonbel (p) == sonbel (fa) ? rotate (fa) : rotate (p); 58 } 59 void Access (int p) { 60 for (int tp = 0; p; tp = p, p = father[p]) 61 splay (p), virsize[p] += subtree[son[p][1]] - subtree[tp], son[p][1] = tp, pushup (p); 62 } 63 void Makeroot (int p) { 64 Access (p), splay (p), reverse (p); 65 } 66 void Split (int x, int y) { 67 Makeroot (x); 68 Access (y), splay (y); 69 } 70 void link (int x, int y) { 71 Split (x, y); 72 father[x] = y, virsize[y] += subtree[x]; 73 pushup (y); 74 } 75 76 int ances[MAXN]; // 重心用并查集维护 77 int find (int p) { 78 return p == ances[p] ? p : ances[p] = find (ances[p]); 79 } 80 81 int N, Q; 82 char opt[5]; 83 84 int Newgrvy (int p) { 85 int half = subtree[p] >> 1; 86 int isodd = subtree[p] & 1; 87 int lsum = 0, rsum = 0; 88 int grvy = N + 1; 89 while (p) { 90 pushdown (p); 91 int l = lsum + subtree[son[p][0]], r = rsum + subtree[son[p][1]]; 92 if (l <= half && r <= half) { 93 if (p < grvy) 94 grvy = p; 95 if (isodd) 96 return grvy; 97 } 98 if (l > r) { 99 rsum += subtree[son[p][1]] + virsize[p] + 1; 100 p = son[p][0]; 101 } 102 else { 103 lsum += subtree[son[p][0]] + virsize[p] + 1; 104 p = son[p][1]; 105 } 106 } 107 splay (grvy); 108 return grvy; 109 } 110 111 int getnum () { 112 int num = 0; 113 char ch = getchar (); 114 115 while (! isdigit (ch)) 116 ch = getchar (); 117 while (isdigit (ch)) 118 num = (num << 3) + (num << 1) + ch - '0', ch = getchar (); 119 120 return num; 121 } 122 123 int ans = 0; 124 int main () { 125 N = getnum (), Q = getnum (); 126 for (int i = 1; i <= N; i ++) 127 subtree[i] = 1, ances[i] = i, ans ^= i; 128 for (int i = 1; i <= Q; i ++) { 129 scanf ("%s", opt + 1); 130 if (opt[1] == 'A') { 131 int x = getnum (), y = getnum (); 132 link (x, y); 133 int fx = find (x), fy = find (y); 134 Split (fx, fy); 135 int grvy = Newgrvy (fy); 136 ans ^= fx ^ fy ^ grvy; 137 ances[fx] = ances[fy] = ances[grvy] = grvy; 138 } 139 else if (opt[1] == 'Q') { 140 int p = getnum (); 141 printf ("%d\n", find (p)); 142 } 143 else if (opt[1] == 'X') 144 printf ("%d\n", ans); 145 } 146 147 return 0; 148 } 149 150 /* 151 10 10 152 Xor 153 Q 1 154 A 10 1 155 A 1 4 156 Q 4 157 Q 10 158 A 7 6 159 Xor 160 Q 7 161 Xor 162 */