[BZOJ3510][洛谷P4299]首都

    xiaoxiao2022-07-03  135

    Solution

    显然首都即树的重心。

    考虑动态维护每棵树的重心,当连边 x → y x→y xy 时 设连边之前, x , y x,y x,y 所在树的重心为分别为 G x , G y G_x,G_y Gx,Gy,那么连边后新树的重心 z z z 一定在 G x → G y G_x→G_y GxGy 的路径上。 记路径上 A u A_u Au B u B_u Bu 是路径上某一点 u u u 的左右两个相邻节点,且满足 G x , G y G_x,G_y Gx,Gy 分别在子树 A u , B u A_u,B_u Au,Bu 中,显然 u u u 的最大子树只会是 A u , B u A_u,B_u Au,Bu 其中之一。

    考虑在这条链上二分,二分到点 u u u 时,设树大小为 n n n,若 m a x ( s z e [ A u ] , s z e [ B u ] ) ≤ n / 2 max(sze[A_u],sze[B_u])≤n/2 max(sze[Au],sze[Bu])n/2,则 u u u 为重心。

    但是题目要求找编号最小的重心,所以不论 u u u 是否为重心,都要继续二分下去,如果 s z e [ A u ] > s z e [ B u ] sze[A_u]>sze[B_u] sze[Au]>sze[Bu],就往 A u A_u Au 那边二分,否则往 B u B_u Bu 那边二分。

    问题转化为如何求 s z e sze sze

    类比 L C T LCT LCT,把 G x → G y G_x→G_y GxGy 看作一条实路径,除此路径之外全是虚边,那么知道 G x → A u , B u → G y G_x→A_u,B_u→G_y GxAuBuGy 每个点的虚子树大小就好做了。

    L C T LCT LCT 维护虚子树大小详见这里。

    维护每棵树的重心可以用并查集,时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

    Code

    #include <bits/stdc++.h> using namespace std; template <class t> inline void read(t & res) { char ch; while (ch = getchar(), !isdigit(ch)); res = ch ^ 48; while (ch = getchar(), isdigit(ch)) res = res * 10 + (ch ^ 48); } const int e = 2e5 + 5; int si[e], ans, q[e], s[e], n, lc[e], rc[e], fa[e], m, f[e]; bool rev[e]; inline int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); } inline bool isroot(int x) { return !fa[x] || (lc[fa[x]] != x && rc[fa[x]] != x); } inline bool which(int x) { return rc[fa[x]] == x; } inline void pushdown(int x) { if (rev[x]) { if (lc[x]) rev[lc[x]] ^= 1; if (rc[x]) rev[rc[x]] ^= 1; swap(lc[x], rc[x]); rev[x] = 0; } } inline void upt(int x) { s[x] = s[lc[x]] + s[rc[x]] + si[x] + 1; } inline void rotate(int x) { int y = fa[x], z = fa[y], b = (lc[y] == x ? rc[x] : lc[x]); if (z && !isroot(y)) (lc[z] == y ? lc[z] : rc[z]) = x; fa[x] = z; fa[y] = x; if (b) fa[b] = y; if (lc[y] == x) { lc[y] = b; rc[x] = y; } else { rc[y] = b; lc[x] = y; } upt(y); upt(x); } inline void splay(int x) { int w = 1; q[w = 1] = x; for (int y = x; !isroot(y); y = fa[y]) q[++w] = fa[y]; for (int i = w; i >= 1; i--) pushdown(q[i]); while (!isroot(x)) { if (!isroot(fa[x])) { if (which(x) == which(fa[x])) rotate(fa[x]); else rotate(x); } rotate(x); } } inline void access(int x) { for (int y = 0; x; y = x, x = fa[x]) { splay(x); si[x] += s[rc[x]]; si[x] -= s[y]; rc[x] = y; upt(x); if (y) fa[y] = x; } } inline void makeroot(int x) { access(x); splay(x); rev[x] ^= 1; } inline void link(int x, int y) { makeroot(x); access(y); splay(y); fa[x] = y; si[y] += s[x]; upt(y); } inline void split(int x, int y) { makeroot(y); access(x); splay(x); } inline int ask(int x) { int ls = 0, rs = 0, res = 1e8, mx = s[x] >> 1; while (x) { pushdown(x); int s1 = ls + s[lc[x]], s2 = rs + s[rc[x]]; if (max(s1, s2) <= mx) res = min(res, x); if (s1 > s2) rs += si[x] + 1 + s[rc[x]], x = lc[x]; else ls += si[x] + 1 + s[lc[x]], x = rc[x]; } splay(res); return res; } inline char getc() { char ch; while (ch = getchar(), !isalpha(ch)); char res = ch; while (ch = getchar(), isalpha(ch)); return res; } int main() { int i, x, y; read(n); read(m); for (i = 1; i <= n; i++) s[i] = 1, f[i] = i, ans ^= i; while (m--) { char op = getc(); if (op == 'X') printf("%d\n", ans); else if (op == 'Q') { read(x); printf("%d\n", find(x)); } else { read(x); read(y); link(x, y); x = find(x); y = find(y); split(x, y); int z = ask(x); ans ^= x ^ y ^ z; f[x] = f[y] = f[z] = z; } } return 0; }
    最新回复(0)