Luogu5327【ZJOI2019】语言【树上差分,线段树合并】
题目大意
给定一棵$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】语言【树上差分,线段树合并】的更多相关文章
- [Luogu5327][ZJOI2019]语言(树上差分+线段树合并)
首先可以想到对每个点统计出所有经过它的链的并所包含的点数,然后可以直接得到答案.根据实现不同有下面几种方法.三个log:假如对每个点都存下经过它的链并S[x],那么每新加一条路径进来的时候,相当于在路 ...
- [BZOJ3307] 雨天的尾巴(树上差分+线段树合并)
[BZOJ3307] 雨天的尾巴(树上差分+线段树合并) 题面 给出一棵N个点的树,M次操作在链上加上某一种类别的物品,完成所有操作后,要求询问每个点上最多物品的类型. N, M≤100000 分析 ...
- Luogu5327 ZJOI2019语言(树上差分+线段树合并)
暴力树剖做法显然,即使做到两个log也不那么优美. 考虑避免树剖做到一个log.那么容易想到树上差分,也即要对每个点统计所有经过他的路径产生的总贡献(显然就是所有这些路径端点所构成的斯坦纳树大小),并 ...
- [ZJOI2019]语言——树剖+树上差分+线段树合并
原题链接戳这儿 SOLUTION 考虑一种非常\(naive\)的统计方法,就是对于每一个点\(u\),我们维护它能到达的点集\(S_u\),最后答案就是\(\frac{\sum\limits_{i= ...
- 2018.08.28 洛谷P4556 [Vani有约会]雨天的尾巴(树上差分+线段树合并)
传送门 要求维护每个点上出现次数最多的颜色. 对于每次修改,我们用树上差分的思想,然后线段树合并统计答案就行了. 注意颜色很大需要离散化. 代码: #include<bits/stdc++.h& ...
- bzoj 3307: 雨天的尾巴【树剖lca+树上差分+线段树合并】
这居然是我第一次写线段树合并--所以我居然在合并的时候加点结果WAWAWAMLEMLEMLE--!ro的时候居然直接指到la就行-- 树上差分,每个点建一棵动态开点线段树,然后统计答案的时候合并即可 ...
- BZOJ 3307 雨天的尾巴 (树上差分+线段树合并)
题目大意:给你一棵树,树上一共n个节点,共m次操作,每次操作给一条链上的所有节点分配一个权值,求所有节点被分配到所有的权值里,出现次数最多的权值是多少,如果出现次数相同就输出最小的. (我辣鸡bzoj ...
- P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并 (树上差分+线段树合并)
显然的树上差分问题,最后要我们求每个点数量最多的物品,考虑对每个点建议线段树,查询子树时将线段树合并可以得到答案. 用动态开点的方式建立线段树,注意离散化. 1 #include<bits/st ...
- [NOIP2016]天天爱跑步(树上差分+线段树合并)
将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...
- [Vani有约会]雨天的尾巴(树上差分+线段树合并)
首先村落里的一共有n座房屋,并形成一个树状结构.然后救济粮分m次发放,每次选择两个房屋(x,y),然后对于x到y的路径上(含x和y)每座房子里发放一袋z类型的救济粮. 然后深绘里想知道,当所有的救济粮 ...
随机推荐
- JS 05 json
1.下载jar包: https://www.github.com/alibaba/fastjson/releases https://www.mvnrepository.com/artifact/co ...
- 基于搜索的贝叶斯网络结构学习算法-K2
基于搜索的贝叶斯网络结构学习算法-K2 2018-04-05 19:34:18 ItsBlue 阅读数 3172更多 分类专栏: 贝叶斯网络 网络结构学习 版权声明:本文为博主原创文章,遵循CC ...
- CSS3中三种清除浮动(float)影响的方式
float是HTML中布局的一大关键,很多难题一旦用上float都能很愉快地解决.但是凡是好用的,也容易出错.比如当子元素都为float时,其父元素会受影响,或者偶尔会发现自己某个div的高度变成了0 ...
- 【转载】springboot启动报错(Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWe)
SpringBoot启动时的异常信息如下: 1 "C:\Program Files\Java\jdk1.8.0_161\bin\java" ......... com.fangxi ...
- 三种TCP协议聊天室实现
一 概述 使用Java的IO实现聊天室 使用Java的NIO实现聊天室 使用Netty实现聊天室 二 IO聊天室 1 服务器 public class IOServer { public static ...
- CentOS7/RHEL6下,如何查看目录与子目录大小
原文:CentOS7/RHEL6下,如何查看目录与子目录大小 通过强大的[du]命令,可以帮助我们快捷的查看目录的大小,非常实用. du命令用来查看目录或文件所占用磁盘空间的大小.常用选项组合为:du ...
- ASP.NET WEB应用程序(.network4.5)MVC 工作原理
MVC就是模型.视图.控制器. 项目中控制器对应Controllers目录,视图对应Views目录,模型对应Models目录. 1.当我们创建一个控制器时,比如在Controllers目录新建一个名字 ...
- 一步一步教你实现iOS音频频谱动画(一)
如果你想先看看最终效果再决定看不看文章 -> bilibili 示例代码下载 第二篇:一步一步教你实现iOS音频频谱动画(二) 基于篇幅考虑,本次教程分为两篇文章,本篇文章主要讲述音频播放和频谱 ...
- cocos发布遇到的问题
学习第二天,用官方的demo进行打包,出现以下问题: 第一个问题: 报错信息:scene 没有保存,请先保存相关信息再进行构建. 解决方案:ctrl+s保存即可,一开始没注意前面的英文是场景的意思 第 ...
- Vue粒子特效(vue-particles插件)
` npm install vue-particles --save-dev ` ` import VueParticles from 'vue-particles' Vue.use(VueParti ...