这题 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. 【原创】c# Winform 使用 web 的UrlEncode/UrlDecode 方法

    1.先 右键引用,添加 System.Web 数据集 2.语句如下 string s= System.Web.HttpUtility.UrlEncode("123", System ...

  2. MT【132】倒序相加因式分解

    设数列\(\{a_n\}\)的前\(n\)项和\(S_n\)满足\(S_{n+1}=a_2S_n+a_1,\)其中\(a_2\ne 0\)且\(a_2>-1\) 求证:\(S_n\le \dfr ...

  3. THUWC2017随机二分图

    题面链接 洛谷 sol 唯一的重点是拆边... 0的不管,只看1.2. 先无论如何把两条边的边权赋为\(0.5\)然后我们发现如果两个都选了. 对于第一种边,我们发现如果\(\frac{1}{2} * ...

  4. 【BZOJ1864】三色二叉树(动态规划)

    [BZOJ1864]三色二叉树(动态规划) 题面 BZOJ 题解 首先把树给构出来. 设\(f[i][0/1]\)表示当前节点\(i\),是否是绿色节点的子树中最大/最小的绿色节点的个数和. 转移很显 ...

  5. 【转】VMware虚拟机三种网络模式详解

    由于Linux目前很热门,越来越多的人在学习Linux,但是买一台服务放家里来学习,实在是很浪费.那么如何解决这个问题?虚拟机软件是很好的选择,常用的虚拟机软件有VMware Workstations ...

  6. BZOJ2213 [Poi2011]Difference 【乱搞】

    题目链接 BZOJ2213 题解 考虑任意一对点的贡献,单独拿出那些点所在位置 一个设为\(1\),一个设为\(-1\),从头到尾扫一遍维护前缀和,以及当前最小前缀和 两者相减更新答案 需要注意的是当 ...

  7. 构建工具-----Gradle-----安装配置

    介绍 Gradle 是一款构建工具,继 Ant .Maven 之后的现代构建工具. 下载 Gradle 下面是 Gradle 的官方网站地址: Gradle l Modern Open-Source ...

  8. Java考试题之十

    QUESTION 230 Given: 10. class One { 11. public One foo() { return this; } 12. } 13. class Two extend ...

  9. cpplint

    Cpplint是一个Python脚本,作为一款开源免费的代码静态检测工具,Google也使用它作为自己的C++代码检测工具,也就是说,只要你的代码遵从Google C++代码规范,那么Cpplint将 ...

  10. CMake 案例

    单个源文件 # CMake 最低版本号要求 cmake_minimum_required (VERSION 3.11) # 项目信息 project (Demo) # 指定生成目标 add_execu ...