题目大意

给定一棵$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. THUPC2019/CTS2019/APIO2019游记

    Day -? 居然还能报上thupc,我在队里唯一的作用大约是cfrating稍微高点方便过审.另外两位是lz和xyy. Day -2 我夫人生日! Day -1 lz和xyy的家长都来了带我飞.住在 ...

  2. (三)ActiveMQ之发布- 订阅消息模式实现

    一.概念 发布者/订阅者模型支持向一个特定的消息主题发布消息.0或多个订阅者可能对接收来自特定消息主题的消息感兴趣.在这种模型下,发布者和订阅者彼此不知道对方.这种模式好比是匿名公告板.这种模式被概括 ...

  3. SpringMVC 出现 406(Not Acceptable)

    首先,需要清楚,http state 406代表什么意思: 406是HTTP协议状态码的一种,表示无法使用请求的特性来响应请求的网页.一般指客户端浏览器不接受所请求页面的MIME类型. 出现这样的错误 ...

  4. Effective Java 读书笔记(三):类与接口

    1 最小化类和成员的可访问性 (1)封装 封装对组成系统的组件进行解耦,从而允许这些组件独立开发,测试,优化,使用,理解和修改. 封装提高了软件的复用性,因为组件间的耦合度低使得它们不仅在开发环境,而 ...

  5. INTEL_BIOS 编译—for-ATOM_E3800

    INTEL_BIOS 编译—for-ATOM_E3800 ======================================================================= ...

  6. C++ raw string literal

    raw string literal 以   R"(  开头, )" 结束,是可以跨越多行的字符串字面值,转义字符如 \t\n 在raw string literal中是普通的文本 ...

  7. vue、react中循环遍历为什么会有key,key有什么作用?

    先讲一下,vue和react都是在操作虚拟dom,并且根据diff算法进行新旧dom对比,从而更新dom,以vue举例: vue官方文档中写到有 key 的特殊属性主要用在 Vue 的虚拟 DOM 算 ...

  8. Async await 解析

    Async 定义:使异步函数以同步函数的形式书写(Generator函数语法糖) 原理:将Generator函数和自动执行器spawn包装在一个函数里 形式:将Generator函数的*替换成asyn ...

  9. Eclipse中如何创建一个完整的Maven-Web项目

    Maven Web项目搭建 1.首先确保本地开发环境搭建完毕(jdk,maven). 2.打开Eclipse,新建Maven项目.选择Maven Project选项. 3.将第一项:Create a ...

  10. [LeetCode] 219. Contains Duplicate II ☆(存在重复元素2)

    每天一算:Contains Duplicate II 描述 给出1个整形数组nums和1个整数k,是否存在索引i和j,使得nums[i] == nums[j] 且i和j之间的差不超过k Example ...