这题 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)$

代码

 #include <iostream>
#include <cstdio>
#include <cstring> using namespace std; const int MAXN = 1e05 + ; int father[MAXN]= {};
int son[MAXN][]= {};
int subtree[MAXN]= {}, virsize[MAXN]= {};
int revtag[MAXN]= {}; int isroot (int p) {
return son[father[p]][] != p && son[father[p]][] != p;
}
int sonbel (int p) {
return son[father[p]][] == p;
}
void reverse (int p) {
if (! p)
return ;
swap (son[p][], son[p][]);
revtag[p] ^= ;
}
void pushup (int p) {
subtree[p] = subtree[son[p][]] + subtree[son[p][]] + virsize[p] + ;
}
void pushdown (int p) {
if (revtag[p]) {
reverse (son[p][]), reverse (son[p][]);
revtag[p] = ;
}
}
void rotate (int p) {
int fa = father[p], anc = father[fa];
int s = sonbel (p);
son[fa][s] = son[p][s ^ ];
if (son[fa][s])
father[son[fa][s]] = fa;
if (! isroot (fa))
son[anc][sonbel (fa)] = p;
father[p] = anc;
son[p][s ^ ] = fa, father[fa] = p;
pushup (fa), pushup (p);
}
int Stack[MAXN];
int top = ;
void splay (int p) {
top = , Stack[++ top] = p;
for (int nd = p; ! isroot (nd); nd = father[nd])
Stack[++ top] = father[nd];
while (top > )
pushdown (Stack[top]), top --;
for (int fa = father[p]; ! isroot (p); rotate (p), fa = father[p])
if (! isroot (fa))
sonbel (p) == sonbel (fa) ? rotate (fa) : rotate (p);
}
void Access (int p) {
for (int tp = ; p; tp = p, p = father[p])
splay (p), virsize[p] += subtree[son[p][]] - subtree[tp], son[p][] = tp, pushup (p);
}
void Makeroot (int p) {
Access (p), splay (p), reverse (p);
}
void Split (int x, int y) {
Makeroot (x);
Access (y), splay (y);
}
void link (int x, int y) {
Split (x, y);
father[x] = y, virsize[y] += subtree[x];
pushup (y);
} int ances[MAXN]; // 重心用并查集维护
int find (int p) {
return p == ances[p] ? p : ances[p] = find (ances[p]);
} int N, Q;
char opt[]; int Newgrvy (int p) {
int half = subtree[p] >> ;
int isodd = subtree[p] & ;
int lsum = , rsum = ;
int grvy = N + ;
while (p) {
pushdown (p);
int l = lsum + subtree[son[p][]], r = rsum + subtree[son[p][]];
if (l <= half && r <= half) {
if (p < grvy)
grvy = p;
if (isodd)
return grvy;
}
if (l > r) {
rsum += subtree[son[p][]] + virsize[p] + ;
p = son[p][];
}
else {
lsum += subtree[son[p][]] + virsize[p] + ;
p = son[p][];
}
}
splay (grvy);
return grvy;
} int getnum () {
int num = ;
char ch = getchar (); while (! isdigit (ch))
ch = getchar ();
while (isdigit (ch))
num = (num << ) + (num << ) + ch - '', ch = getchar (); return num;
} int ans = ;
int main () {
N = getnum (), Q = getnum ();
for (int i = ; i <= N; i ++)
subtree[i] = , ances[i] = i, ans ^= i;
for (int i = ; i <= Q; i ++) {
scanf ("%s", opt + );
if (opt[] == 'A') {
int x = getnum (), y = getnum ();
link (x, y);
int fx = find (x), fy = find (y);
Split (fx, fy);
int grvy = Newgrvy (fy);
ans ^= fx ^ fy ^ grvy;
ances[fx] = ances[fy] = ances[grvy] = grvy;
}
else if (opt[] == 'Q') {
int p = getnum ();
printf ("%d\n", find (p));
}
else if (opt[] == 'X')
printf ("%d\n", ans);
} return ;
} /*
10 10
Xor
Q 1
A 10 1
A 1 4
Q 4
Q 10
A 7 6
Xor
Q 7
Xor
*/

BZOJ 3510 - 首都 「 $LCT$ 动态维护树的重心」的更多相关文章

  1. BZOJ 3510: 首都 LCT + multiset维护子树信息 + 树的重心

    Code: #include<bits/stdc++.h> #define maxn 200000 #define inf 1000000000 using namespace std; ...

  2. 2019 ICPC上海网络赛 A 题 Lightning Routing I (动态维护树的直径)

    题目: 给定一棵树, 带边权. 现在有2种操作: 1.修改第i条边的权值. 2.询问u到其他一个任意点的最大距离是多少. 题解: 树的直径可以通过两次 dfs() 的方法求得.换句话说,到任意点最远的 ...

  3. BZOJ.3510.首都(LCT 启发式合并 树的重心)

    题目链接 BZOJ 洛谷 详见这. 求所有点到某个点距离和最短,即求树的重心.考虑如何动态维护. 两棵子树合并后的重心一定在两棵树的重心之间那条链上,所以在合并的时候用启发式合并,每合并一个点检查sz ...

  4. BZOJ 3510 首都 (LCT)

    洛谷P4299传送门 题目大意:给你一颗树,边是一条一条连上去的 在连接过程中会存在询问,询问当前节点所在联通块(其实是一颗树)的重心是哪个节点 以及森林中所有树的重心的异或和 在做这道题之前,要先了 ...

  5. 计蒜客D2T2 蒜头君的排序(动态维护树状数组)

    蒜头君的排序(sort) 2000ms 262144K 蒜头君是一个爱思考的好孩子,这一天他学习了冒泡排序,于是他就想,把一个乱序排列通过冒泡排序排至升序需要多少次交换,这当然难不倒他,于是他想来点刺 ...

  6. 【刷题】BZOJ 3510 首都

    Description 在X星球上有N个国家,每个国家占据着X星球的一座城市.由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的. X星球上战乱频发,如果A国打败了B国,那么B国将永远从 ...

  7. 洛谷P4299 首都(BZOJ3510)(LCT,树的重心,二分查找)

    Update:原来的洛谷U21715已成坑qwq 已经被某位管理员巨佬放进公共题库啦!又可以多一个AC记录啦! 洛谷题目传送门 其实也可以到这里交啦 思路分析 动态维护树的重心 题目中说到国家的首都会 ...

  8. BZOJ 3924 ZJOI2015 幻想乡战略游戏 树链剖分

    题目链接:https://www.luogu.org/problemnew/show/P3345(bzoj权限题) 题意概述:动态维护树的上所有点到这棵树的带权重心的距离和.N,Q<=10000 ...

  9. ACM-树重心的性质及动态维护

    本文转自http://fanhq666.blog.163.com/blog/static/81943426201172472943638/ 求树重心的方法:(NlogN) http://www.cnb ...

随机推荐

  1. 【刷题】BZOJ 4816 [Sdoi2017]数字表格

    Description Doris刚刚学习了fibonacci数列.用f[i]表示数列的第i项,那么 f[0]=0 f[1]=1 f[n]=f[n-1]+f[n-2],n>=2 Doris用老师 ...

  2. 【poj1390】 Blocks

    http://poj.org/problem?id=1390 (题目链接) 题意 给出一排方块,每次可以把颜色相同的消掉,获得长度的平方的分数,问最大得分. Solution 蜜汁dp.. 我们把颜色 ...

  3. 实现运行在独立线程池的调度功能,基于Spring和Annotation

    使用Spring的注解(@Scheduled)声明多个调度的时候,由于其默认实现机制,将导致多个调度方法之间相互干扰(简单理解就是调度不按配置的时间点执行). 为了解决该问题尝试了修改线程池大小,但是 ...

  4. BZOJ 3505 [Cqoi2014]数三角形

    3505: [Cqoi2014]数三角形 Description 给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个.下图为4x4的网格上的一个三角形.注意三角形的三点不能共线. Input ...

  5. 【贪心/Trie】【CF1083B】 The Fair Nut and Strings

    Description 有 \(k\) 个长度为 \(n\) 的只含 \(a\) 或 \(b\) 字符串,并不知道它们具体是多少,只知道它们的字典序不小于字符串 \(A\),同时不大于字符串 \(B\ ...

  6. 【DP/数学】【CF1061C】 Multiplicity

    Description 给定一个序列 \(a\),求有多少非空序列 \(b\) 满足 \(b\) 是 \(a\) 的子序列并且 \(\forall~k~\in~[1,len_b],~~k \mid b ...

  7. SSO基于cas的登录

    概念介绍 1.定义 CAS ( CentralAuthentication Service ) 是 Yale 大学发起的一个企业级的.开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法 ...

  8. C#实现执行数据库事务案例

    我是实际项目来拆出来做的案例,场景是比如我们在做电商网站时,在创建订单时的一系列操作,通常在创建订单时我们需要数据表的操作有:订单的表头(主表).订单的详细.清空购物车.甚至是修改优惠劵的状态(在使用 ...

  9. C++持有Object-C对象时容易内存泄露

    在IOS项目中,可以将C++与Object-C混编,不过必须放在实现文件.mm中. 在.mm中,我们可能创建了一个C++对象A,而它持有一个Object-C对象B作为成员变量.当A对象被释放掉的时候, ...

  10. .Net多线程之线程安全

    ConcurrentDictionary是.net4.0推出的一套线程安全集合里的其中一个,和它一起被发行的还有ConcurrentStack,ConcurrentQueue等类型,它们的单线程版本( ...