题目大意

给定一棵$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. 深度剖析Kubernetes API Server三部曲 - part 3

    在本系列的前两部分中我们介绍了API Server的总体流程,以及API对象如何存储到etcd中.在本文中我们将探讨如何扩展API资源. 在一开始的时候,扩展API资源的唯一方法是扩展相关API源代码 ...

  2. Python 的 Mixin 类(转)

    转1:https://www.cnblogs.com/aademeng/articles/7262520.html 转2:https://blog.csdn.net/u010377372/articl ...

  3. IDEA GIT 忽略文件 最佳方式

    前言 转载一篇博客,简单,实用. 原文地址:intellij idea 忽略文件不提交 ps:下面均为转载博客的内容: 在intellij中忽略提交文件,分两种情况, 文件没有纳入版本管理 第一种,文 ...

  4. hdu 2089 入手数位dp问题

    数位dp解决的问题是指求在一段数的区间里面 满足条件的数的个数 核心为两点 http://wenku.baidu.com/link?url=tpfIYzhx_MzevpIM58UZ66pr-87MCF ...

  5. 清空windows系统网络配置

    清空windows系统网络配置 来源  https://www.cnblogs.com/lemon-rain/p/9569990.html 具体描述:qq,微信可用网,但其他不能用. 一.win+r ...

  6. CPU的基本组成

    1.CPU是用来运算的(加法运算.乘法运算.逻辑运算(与.或.非)等) 2.运算操作涉及到数据输入(input).处理.数据输出(output).A和B是输入数据,加法运算时处理.C是输出数据. 3. ...

  7. sql复杂的子查询,横向合并结果集

    第一个查询的结果集 select * from( select c.msName,a.msId,c.msPrice, c.msPrice*COUNT(a.msId) as totalMoney,sum ...

  8. C++ 容器一图以蔽之

    读完C++ primary 容器相关章节,有必要总结一下容器的要点,一图说明. 其中的问题,以下是我的一些想法,欢迎交流. 问题1. STL源码剖析 · vector 问题2. STL源码剖析 · R ...

  9. 第五章、Celery分布式系统

    Celery 官方 Celery 官网:http://www.celeryproject.org/ Celery 官方文档英文版:http://docs.celeryproject.org/en/la ...

  10. OGG 自动重启脚本

    6-20 * * * /oggdata/log/oggautorestart.sh >/oggdata/log/crontab_oggautorestart.log 2>&1 [说 ...