题目大意

给定一棵$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

 #include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = , M = N << ;
vector<int> Edge[N], Del[N];
int n, m, fa[N], st[][N], dfn[N], tim, tot, pre[N], dep[N];
inline void dfs1(int x){
dfn[x] = ++ tim;
pre[x] = ++ tot; st[][tot] = x;
for(Rint v : Edge[x]) if(v != fa[x]) {
fa[v] = x;
dep[v] = dep[x] + ;
dfs1(v);
st[][++ tot] = x;
}
}
int lg2[N];
inline void init(int k){
lg2[] = ;
for(Rint i = ;i <= k;i ++) lg2[i] = lg2[i >> ] + ;
for(Rint i = ;i <= ;i ++)
for(Rint j = ;j <= k - ( << i - );j ++)
st[i][j] = dep[st[i - ][j]] < dep[st[i - ][j + ( << i - )]] ? st[i - ][j] : st[i - ][j + ( << i - )];
}
inline int LCA(int u, int v){
u = pre[u]; v = pre[v];
if(u > v) swap(u, v);
int k = lg2[v - u + ];
return dep[st[k][u]] < dep[st[k][v - ( << k) + ]] ? st[k][u] : st[k][v - ( << k) + ];
}
int rt[N], seg[M], c[M], s[M], t[M], ls[M], rs[M], num;
/*
rt : root nodes
seg : \sum_{x\in T} dep[x]
c : \sum_{x\in T} 1
s : \min_{x\in T} x
t : \max_{x\in T} x
ls, rs : left/right node
*/
inline void pushup(int x){
seg[x] = seg[ls[x]] + seg[rs[x]] - dep[LCA(t[ls[x]], s[rs[x]])];
s[x] = s[ls[x]] ? s[ls[x]] : s[rs[x]];
t[x] = t[rs[x]] ? t[rs[x]] : t[ls[x]];
}
inline int query(int x){return seg[x] - dep[LCA(s[x], t[x])];}
inline void change(int &x, int p, int v, int L, int R){
if(!x) x = ++ num;
if(L == R){
c[x] += v;
seg[x] = c[x] ? dep[p] : ;
s[x] = t[x] = c[x] ? p : ;
return;
}
int mid = L + R >> ;
if(dfn[p] <= mid) change(ls[x], p, v, L, mid);
else change(rs[x], p, v, mid + , R);
pushup(x);
}
inline void merge(int &x, int v, int L, int R){
if(!x || !v){x |= v; return;}
if(L == R){
c[x] += c[v]; seg[x] |= seg[v]; s[x] |= s[v]; t[x] |= t[v];
return;
}
int mid = L + R >> ;
merge(ls[x], ls[v], L, mid);
merge(rs[x], rs[v], mid + , R);
pushup(x);
}
LL ans;
inline void dfs2(int x){
for(Rint v : Edge[x])
if(v != fa[x]) dfs2(v);
for(Rint v : Del[x])
change(rt[x], v, -, , n);
ans += query(rt[x]);
if(fa[x]) merge(rt[fa[x]], rt[x], , n);
}
int main(){
scanf("%d%d", &n, &m);
for(Rint i = ;i < n;i ++){
int a, b;
scanf("%d%d", &a, &b);
Edge[a].push_back(b);
Edge[b].push_back(a);
}
dfs1(); init(tot);
for(Rint i = ;i <= m;i ++){
int u, v, lca;
scanf("%d%d", &u, &v);
lca = LCA(u, v);
// printf("u = %d, v = %d, lca = %d\n", u, v, lca);
change(rt[u], v, , , n); change(rt[v], u, , , n);
change(rt[v], v, , , n); change(rt[u], u, , , n);
Del[lca].push_back(u); Del[lca].push_back(v);
if(fa[lca]) Del[fa[lca]].push_back(u), Del[fa[lca]].push_back(v);
}
// for(Rint i = 1;i <= n;i ++)
// printf("dep[%d] = %d\n", i, dep[i]);
dfs2();
printf("%lld\n", ans >> );
}

Luogu5327

闲聊

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

Luogu5327【ZJOI2019】语言【树上差分,线段树合并】的更多相关文章

  1. [Luogu5327][ZJOI2019]语言(树上差分+线段树合并)

    首先可以想到对每个点统计出所有经过它的链的并所包含的点数,然后可以直接得到答案.根据实现不同有下面几种方法.三个log:假如对每个点都存下经过它的链并S[x],那么每新加一条路径进来的时候,相当于在路 ...

  2. [BZOJ3307] 雨天的尾巴(树上差分+线段树合并)

    [BZOJ3307] 雨天的尾巴(树上差分+线段树合并) 题面 给出一棵N个点的树,M次操作在链上加上某一种类别的物品,完成所有操作后,要求询问每个点上最多物品的类型. N, M≤100000 分析 ...

  3. Luogu5327 ZJOI2019语言(树上差分+线段树合并)

    暴力树剖做法显然,即使做到两个log也不那么优美. 考虑避免树剖做到一个log.那么容易想到树上差分,也即要对每个点统计所有经过他的路径产生的总贡献(显然就是所有这些路径端点所构成的斯坦纳树大小),并 ...

  4. [ZJOI2019]语言——树剖+树上差分+线段树合并

    原题链接戳这儿 SOLUTION 考虑一种非常\(naive\)的统计方法,就是对于每一个点\(u\),我们维护它能到达的点集\(S_u\),最后答案就是\(\frac{\sum\limits_{i= ...

  5. 2018.08.28 洛谷P4556 [Vani有约会]雨天的尾巴(树上差分+线段树合并)

    传送门 要求维护每个点上出现次数最多的颜色. 对于每次修改,我们用树上差分的思想,然后线段树合并统计答案就行了. 注意颜色很大需要离散化. 代码: #include<bits/stdc++.h& ...

  6. bzoj 3307: 雨天的尾巴【树剖lca+树上差分+线段树合并】

    这居然是我第一次写线段树合并--所以我居然在合并的时候加点结果WAWAWAMLEMLEMLE--!ro的时候居然直接指到la就行-- 树上差分,每个点建一棵动态开点线段树,然后统计答案的时候合并即可 ...

  7. BZOJ 3307 雨天的尾巴 (树上差分+线段树合并)

    题目大意:给你一棵树,树上一共n个节点,共m次操作,每次操作给一条链上的所有节点分配一个权值,求所有节点被分配到所有的权值里,出现次数最多的权值是多少,如果出现次数相同就输出最小的. (我辣鸡bzoj ...

  8. P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并 (树上差分+线段树合并)

    显然的树上差分问题,最后要我们求每个点数量最多的物品,考虑对每个点建议线段树,查询子树时将线段树合并可以得到答案. 用动态开点的方式建立线段树,注意离散化. 1 #include<bits/st ...

  9. [NOIP2016]天天爱跑步(树上差分+线段树合并)

    将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...

  10. [Vani有约会]雨天的尾巴(树上差分+线段树合并)

    首先村落里的一共有n座房屋,并形成一个树状结构.然后救济粮分m次发放,每次选择两个房屋(x,y),然后对于x到y的路径上(含x和y)每座房子里发放一袋z类型的救济粮. 然后深绘里想知道,当所有的救济粮 ...

随机推荐

  1. java异常那些事

    异常的基本定义: 异常情形是指阻止当前方法或者作用域继续执行的问题.在这里一定要明确一点:异常代码某种程度的错误,尽管Java有异常处理机制,但是我们不能以“正常”的眼光来看待异常,异常处理机制的原因 ...

  2. mysql中的锁机制之概念篇

    锁的概念 ①.锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具. ②.在计算机中,是协调多个进程或线程并发访问某一资源的一种机制. ③.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等 ...

  3. CMake入门-01-从HelloWorld开始

    工作环境 系统:macOS Mojave 10.14.6 CMake: Version 3.15.0-rc4 从 Hello,World! 开始 (1) 新建 hello 目录,创建文件 CMakeL ...

  4. (五)Redis之List

    一.List常用命令 两端添加 两端弹出 1.2. 两端添加和两端弹出 package myRedis01; import java.util.HashMap; import java.util.Li ...

  5. javadoc 自动生成java帮助文档

    用法: javadoc [options] [packagenames] [sourcefiles] 选项: -public 仅显示 public 类和成员 -protected 显示 protect ...

  6. Python中的一些常用模块1

    OS模块,sys模块,time模块,random模块,序列化模块 os模块是与操作系统交互的一个接口 OS模块简单的来说是一个Python的系统编程操作模块,可以处理文件和目录这些我们日常手动需要做的 ...

  7. Vue路由相关配置

    什么是路由? 1.在以前页面跳转使用的是超链接a标签或者js location.href,而路由是跳转切换组件的跳转方式 2.路由就是监听url的改变并提供相对应的组件用于展示 3.vue-route ...

  8. Python 3.7的新特性

    Python 3.7为数据处理.脚本编译和垃圾收集优化以及更快的异步I/O添加了许多新类.python是一种旨在简化复杂任务的语言.python 3.7的最新版本已经正式休闲鹿进入beta发布阶段.P ...

  9. Vue子父组件方法互调

    讲干货,不啰嗦,大家在做vue开发过程中经常遇到父组件需要调用子组件方法或者子组件需要调用父组件的方法的情况,现做一下总结,希望对大家有所帮助. 父组件调用子组件方法: 1.设置子组件的ref,父组件 ...

  10. leetcode-29.两数相除(不用乘除法和mod)

    如题,不用乘除法和mod实现两数相除. 这里引用一位clever boy 的解法. class Solution { public: int divide(int dividend, int divi ...