题目大意

给定一棵$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. 如何在 arm 官网上找到合适的手册

    http://infocenter.arm.com/help/advanced/help.jsp 在这里输入合适的版号即可 这样就可以不用去 CSDN 了 100000_0000_00_EN - AR ...

  2. ajax post上传数据时,前端出现的跨域权限问题:ccess to XMLHttpRequest at ‘’rom origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok st

    本人前端使用多个框架时,jq  ajax传参出现如下报错: 最后发现,可能是xhr的相关默认参数被修改了.顾使用jq 传参时,一直报错,jq  ajax额外添加的关键参数: crossDomain: ...

  3. django静态文件配置和使用

    一.首先需要了解的知识点是: 1.出于对效率和安全的考虑,django管理静态文件的功能仅限于在开发阶段的debug模式下使用,且需要在配置文件的INSTALLED_APPS中加入django.con ...

  4. 洛谷 P1540 机器翻译

    链接:https://www.luogu.org/problemnew/show/p1540 题目: 题目背景 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章. 题目描述 这个翻译 ...

  5. JavaScript--关于闭包(closure)

    js代码在执行前会做的几件事情: 1.代码检测 2.预编译:在执行代码之前会对代码中的函数以及变量提前声明 并且做一些其他的处理 1.函数在执行前的一瞬间,会生成一个OA(object action) ...

  6. DNSMaper 一款子域名枚举与地图标记工具

    DNSMaper DNSMaper拥有与众多子域名枚举工具相似的功能,诸如域传送漏洞检测,子域名枚举,IP地址获取 文件说明├── dnsmaper.py(核心代码)├── dnsmapper.png ...

  7. keepalived+lvs+usp安装实施文档

    操作系统平台:RedHat6.4  x86_64 软件:LVS+keepalived LVS+Keepalived 介绍 LVS LVS是Linux Virtual Server的简写,意即Linux ...

  8. python-----将图片与标注的xml坐标水平翻转

    我们做机器学习的时候,总会用到很多训练集,然后我们的数据比较少的时候,就可以将图片翻转标注.代码如下: #!/usr/bin/env python # -*- coding: utf-8 -*- # ...

  9. docker在Windows环境下的安装

    Windows环境下安装 docker有两种安装包 一.Docker for Windows(目前只能在 64 位的 Windows10 专业版.企业版.教育版下才能安装) 二.Docker Tool ...

  10. python实现pow函数(求n次幂,求n次方)

    目录 类型一:求n次幂 类型二:求n开方 类型一:求n次幂 实现 pow(x, n),即计算 x 的 n 次幂函数.其中n为整数.pow函数的实现--leetcode 解法1:暴力法 不是常规意义上的 ...