博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Luogu5327【ZJOI2019】语言【树上差分,线段树合并】
阅读量:5010 次
发布时间:2019-06-12

本文共 4495 字,大约阅读时间需要 14 分钟。

题目大意

给定一棵$n$个节点的树,维护$n$个集合,一开始第$i$个集合只有节点$i$。有$m$个操作,每次操作输入一个$(u,v)$,表示将$(u,v)$这条链上所有点所属的集合合并。求有多少个无序数对$(u,v)$使得$u,v$同在一个集合之中。

$$n,m\leq 10^5$$

思路

其实这道题要维护的就是极大联通子集使得这个集合的节点都讲同一种语言(以下简称联通块)的合并和维护大小。

设$s_x$表示$x$所在的联通块的大小,$ans=\frac{1}{2}\sum_{x}s_x$.

首先我们发现一个操作$(u,v)$即使将沿途上的联通块都合并在一起,这个问题目前还非常麻烦,但我们可以考虑一个稍微简单的问题,如何将$u$这个点加到$S$集合中。

这其实非常类似于建虚树,将$u$加入$S$其实就是找一个$S$中距离$u$最近的一个节点$v$,然后将$(u,v)$这条链上的点计入联通块,这个集合就会多$dep_u-dep_{lca(u,v)}$。

如果我们按照dfs序的顺序来更新的话,那么$v$这个点其实就是上一个加入的节点。

那对于第一个,即dfs序最小的那个点应该怎么办?先假设1号点在这个集合,但其实$(1,dep_{lca})$这条链上是不算的($lca$表示所有节点的LCA,但其实就是dfs序最大的节点和最小的节点的LCA),所以查询的时候还要减去。

我们对每个节点都维护这么一个集合,考虑通过值域线段树(下标为dfs序)来维护,$[l,r]$维护这段区间中dfs序最大/最小的节点,和所有节点$dep$之和减去所有相邻节点lca的dep之和,查询的时候就是这个值减去dfs序最大、最小的两个点的lca的深度

合并$(u,v)$沿途的联通块就是将这条链上所有节点的线段树中插入$u,v$两个节点

对一条链上的线段树同时操作可以使用树上差分,合并到父亲的时候可以使用线段树合并。

使用RMQ求lca,总时间复杂度$O(n\log n)$,空间复杂度$O(n\log n)$。

实现

注意在线段树合并的时候,对于这种树上的问题,可以将子节点的线段树合并进父节点的线段树而不用多开一个新的线段树,这样可以节约空间,但对于其他情况就需要考虑一下了。

Code

1 #include
2 #define Rint register int 3 using namespace std; 4 typedef long long LL; 5 const int N = 200003, M = N << 6; 6 vector
Edge[N], Del[N]; 7 int n, m, fa[N], st[18][N], dfn[N], tim, tot, pre[N], dep[N]; 8 inline void dfs1(int x){ 9 dfn[x] = ++ tim; 10 pre[x] = ++ tot; st[0][tot] = x; 11 for(Rint v : Edge[x]) if(v != fa[x]) { 12 fa[v] = x; 13 dep[v] = dep[x] + 1; 14 dfs1(v); 15 st[0][++ tot] = x; 16 } 17 } 18 int lg2[N]; 19 inline void init(int k){ 20 lg2[1] = 0; 21 for(Rint i = 2;i <= k;i ++) lg2[i] = lg2[i >> 1] + 1; 22 for(Rint i = 1;i <= 17;i ++) 23 for(Rint j = 1;j <= k - (1 << i - 1);j ++) 24 st[i][j] = dep[st[i - 1][j]] < dep[st[i - 1][j + (1 << i - 1)]] ? st[i - 1][j] : st[i - 1][j + (1 << i - 1)]; 25 } 26 inline int LCA(int u, int v){ 27 u = pre[u]; v = pre[v]; 28 if(u > v) swap(u, v); 29 int k = lg2[v - u + 1]; 30 return dep[st[k][u]] < dep[st[k][v - (1 << k) + 1]] ? st[k][u] : st[k][v - (1 << k) + 1]; 31 } 32 int rt[N], seg[M], c[M], s[M], t[M], ls[M], rs[M], num; 33 /* 34 rt : root nodes 35 seg : \sum_{x\in T} dep[x] 36 c : \sum_{x\in T} 1 37 s : \min_{x\in T} x 38 t : \max_{x\in T} x 39 ls, rs : left/right node 40 */ 41 inline void pushup(int x){ 42 seg[x] = seg[ls[x]] + seg[rs[x]] - dep[LCA(t[ls[x]], s[rs[x]])]; 43 s[x] = s[ls[x]] ? s[ls[x]] : s[rs[x]]; 44 t[x] = t[rs[x]] ? t[rs[x]] : t[ls[x]]; 45 } 46 inline int query(int x){
return seg[x] - dep[LCA(s[x], t[x])];} 47 inline void change(int &x, int p, int v, int L, int R){ 48 if(!x) x = ++ num; 49 if(L == R){ 50 c[x] += v; 51 seg[x] = c[x] ? dep[p] : 0; 52 s[x] = t[x] = c[x] ? p : 0; 53 return; 54 } 55 int mid = L + R >> 1; 56 if(dfn[p] <= mid) change(ls[x], p, v, L, mid); 57 else change(rs[x], p, v, mid + 1, R); 58 pushup(x); 59 } 60 inline void merge(int &x, int v, int L, int R){ 61 if(!x || !v){x |= v; return;} 62 if(L == R){ 63 c[x] += c[v]; seg[x] |= seg[v]; s[x] |= s[v]; t[x] |= t[v]; 64 return; 65 } 66 int mid = L + R >> 1; 67 merge(ls[x], ls[v], L, mid); 68 merge(rs[x], rs[v], mid + 1, R); 69 pushup(x); 70 } 71 LL ans; 72 inline void dfs2(int x){ 73 for(Rint v : Edge[x]) 74 if(v != fa[x]) dfs2(v); 75 for(Rint v : Del[x]) 76 change(rt[x], v, -1, 1, n); 77 ans += query(rt[x]); 78 if(fa[x]) merge(rt[fa[x]], rt[x], 1, n); 79 } 80 int main(){ 81 scanf("%d%d", &n, &m); 82 for(Rint i = 1;i < n;i ++){ 83 int a, b; 84 scanf("%d%d", &a, &b); 85 Edge[a].push_back(b); 86 Edge[b].push_back(a); 87 } 88 dfs1(1); init(tot); 89 for(Rint i = 1;i <= m;i ++){ 90 int u, v, lca; 91 scanf("%d%d", &u, &v); 92 lca = LCA(u, v); 93 // printf("u = %d, v = %d, lca = %d\n", u, v, lca); 94 change(rt[u], v, 1, 1, n); change(rt[v], u, 1, 1, n); 95 change(rt[v], v, 1, 1, n); change(rt[u], u, 1, 1, n); 96 Del[lca].push_back(u); Del[lca].push_back(v); 97 if(fa[lca]) Del[fa[lca]].push_back(u), Del[fa[lca]].push_back(v); 98 } 99 // for(Rint i = 1;i <= n;i ++)100 // printf("dep[%d] = %d\n", i, dep[i]);101 dfs2(1);102 printf("%lld\n", ans >> 1);103 }
Luogu5327

闲聊

如果大家想要进一步深入了解线段树合并如何做树上联通块问题,或者是其他树上联通块问题有什么做法,可以看看rxd的《解决树上联通块问题的一些技巧和工具》。

转载于:https://www.cnblogs.com/AThousandMoons/p/11018727.html

你可能感兴趣的文章
curd_3
查看>>
百度地图API示例之设置地图显示范围
查看>>
Java构造方法、重载及垃圾回收
查看>>
.Net Core AES加密解密
查看>>
Spring Quartz实现任务调度
查看>>
python | 桶排序、冒泡排序、选择排序、去重
查看>>
两个Html页面之间值得传递
查看>>
EasyUI datagrid 的多条件查询
查看>>
Mac升级bash到最新版本
查看>>
利用vagrant打包系统--制作自己的box
查看>>
美女与硬币问题
查看>>
计算几何算法概览 (转)
查看>>
Notepad++的ftp远程编辑功能
查看>>
数据库多对多关联表(Python&MySQL)
查看>>
[实变函数]1.2 集合的运算
查看>>
第06天
查看>>
设计模式的征途—5.原型(Prototype)模式
查看>>
iOS10 app连接不上网络的问题
查看>>
结对开发之电梯调度最终稿(徐梦迪&刘博)
查看>>
simple java mail
查看>>