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 */

 

posted @ 2018-12-28 22:26  Colythme  阅读(313)  评论(0编辑  收藏  举报