【bzoj3510】首都 LCT维护子树信息(+启发式合并)
题目描述
在X星球上有N个国家,每个国家占据着X星球的一座城市。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。
X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。
现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理:
1、A x y:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。
2、Q x:询问当前编号为x的城市所在国家的首都。
3、Xor:询问当前所有国家首都编号的异或和。
输入
第一行是整数N,M,表示城市数和需要处理的信息数。
接下来每行是一个信息,格式如题目描述(A、Q、Xor中的某一种)。
输出
输出包含若干行,为处理Q和Xor信息的结果。
样例输入
10 10
Xor
Q 1
A 10 1
A 1 4
Q 4
Q 10
A 7 6
Xor
Q 7
Xor
样例输出
11
1
1
1
2
6
2
题解
LCT维护子树信息(+启发式合并)
这道题也真是强啊,分析了一会码了一会,结果却因为一个傻x错误坑了我一个多小时~
首先,在link时,要把点数少的连接到点数多的上(这其实不应该叫做启发式合并吧)
这样有什么好处?它有两个重要的性质:
1.合并后的重心一定在点数多的树之内,且在连接点到原重心的链上(因为如果在点数少的树之内,重心最后一段的移动路径一定对答案的贡献恒为负,一定不是最优解;而偏移方向不为到连接点方向的话对答案贡献也一定为负)
2.合并后的重心与原重心距离一定不超过点数少的树的点数(假设一个一个插入,偏移距离一定不超过1)
这就可以看出这样做的优势:性质1限定了重心移动的方向,性质2限定了重心移动的距离。
我们考虑:重心发生改变,把它移动的路径分为每次一条边的段,那么每一段对于答案的贡献一定是递减的,直到某一段对答案贡献为负则停止。
那么我们就可以模拟这个过程,将重心设为树根,每次把重心可能的移动路径拿出来,一个一个判断并处理。
嘴上说真简单
实际上,要动态维护子树大小,需要使用LCT维护子树信息。而在LCT中取出重心的移动路径并不是特别容易,需要求出Splay Tree的中序遍历,就要dfs整棵Splay Tree,并在超过范围时停止。
由于重心是固定的,因此将x合并到y上时不能makeroot(y),只能access(y),splay(y)
更复杂的问题是题目不是使用spj,而是强制要求有多个重心时需要选择编号较小的。所以还应该判断编号的影响。
最重要的是,findroot和dfs时都需要pushdown!一开始我在findroot时想起来了,结果到dfs时又忘了,因为这个sb错误zz了一个小时真是气。
代码不是很美观...具体实现可以参考 bzoj4530 。
时间复杂度依然是O(nlog2n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | #include <cstdio> #include <algorithm> #define N 100010 using namespace std; int fa[N] , c[2][N] , rev[N] , si[N] , sum[N] , sta[N] , top , s; char str[5]; void pushup( int x) { sum[x] = sum[c[0][x]] + sum[c[1][x]] + si[x] + 1; } void pushdown( int x) { if (rev[x]) { int l = c[0][x] , r = c[1][x]; swap(c[0][l] , c[1][l]) , swap(c[0][r] , c[1][r]); rev[l] ^= 1 , rev[r] ^= 1 , rev[x] = 0; } } bool isroot( int x) { return c[0][fa[x]] != x && c[1][fa[x]] != x; } void update( int x) { if (!isroot(x)) update(fa[x]); pushdown(x); } void rotate( int x) { int y = fa[x] , z = fa[y] , l = (c[1][y] == x) , r = l ^ 1; if (!isroot(y)) c[c[1][z] == y][z] = x; fa[x] = z , fa[y] = x , fa[c[r][x]] = y , c[l][y] = c[r][x] , c[r][x] = y; pushup(y) , pushup(x); } void splay( int x) { update(x); while (!isroot(x)) { int y = fa[x] , z = fa[y]; if (!isroot(y)) { if ((c[0][y] == x) ^ (c[0][z] == y)) rotate(x); else rotate(y); } rotate(x); } } void access( int x) { int t = 0; while (x) splay(x) , si[x] += sum[c[1][x]] - sum[t] , c[1][x] = t , pushup(x) , t = x , x = fa[x]; } int find( int x) { access(x) , splay(x); while (c[0][x]) pushdown(x) , x = c[0][x]; return x; } void makeroot( int x) { access(x) , splay(x) , swap(c[0][x] , c[1][x]) , rev[x] = 1; } void split( int x , int y) { makeroot(x) , access(y) , splay(y); } void link( int x , int y) { split(x , y) , fa[x] = y , si[y] += sum[x]; } void dfs( int x) { if (!x) return ; pushdown(x); dfs(c[0][x]); if (top > s) return ; sta[++top] = x; if (top > s) return ; dfs(c[1][x]); } int main() { int n , m , i , ret = 0 , x , y , t , tx , ty , r , ts; scanf ( "%d%d" , &n , &m); for (i = 1 ; i <= n ; i ++ ) sum[i] = 1 , ret ^= i; while (m -- ) { scanf ( "%s" , str); if (str[0] == 'A' ) { scanf ( "%d%d" , &x , &y) , tx = find(x) , ty = find(y) , ret ^= tx ^ ty , splay(tx) , splay(ty); if (sum[tx] > sum[ty] || (sum[tx] == sum[ty] && x < y)) swap(x , y) , swap(tx , ty); s = sum[tx] , ts = sum[tx] + sum[ty] , link(x , y) , access(x) , splay(ty); top = 0 , dfs(ty) , r = ty; for (i = 1 ; i <= top ; i ++ ) { splay(sta[i]) , t = si[sta[i]] + 1 + sum[c[1][sta[i]]]; if (ts - t < t || (ts - t == t && sta[i] <= r)) r = sta[i]; else break ; } makeroot(r) , ret ^= r; } else if (str[0] == 'Q' ) scanf ( "%d" , &x) , printf ( "%d\n" , find(x)); else printf ( "%d\n" , ret); } return 0; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?