线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)
闲话
stO猫锟学长,满脑子神仙DS
网上有不少Dalao把线段树分治也归入CDQ分治?
还是听听YCB巨佬的介绍:
狭义:只计算左边对右边的贡献。
广义:只计算外部对内部的贡献。
看来可以理解为广义下的。
不过叫它线段树分治挺形象的啊!
线段树分治思想
我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献。
而在线段树分治中,询问被固定了。按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间。而这个区间,毫无疑问正好对应着询问的一段区间。
于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护。我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构。
具体的实现,仍然要看题目。
例题1
BZOJ4025二分图(点击进入题目)
首先,图是二分图的充要条件是不存在奇环。
这个性质非常好。我们可以维护一棵生成树,假如当前要加入一条边\(x-y\),而生成树中\(x\)到\(y\)的路径上有偶数条边,那么就会形成奇环。如果\(x\)到\(y\)的路径上有奇数条边,就没问题,而且去掉这条边不会影响后续奇偶性的判断。这就让我们想到LCT做法:像WC2005双面棋盘一样,维护关于边删除时间的最大生成树,每次加边并判断。这里不详细展开。
关于维护奇偶性,我们还可以这样想:二分图染色!每次新加入一条边的时候,我们需要保证\(x,y\)之前其中之一未被染色或已染上不同颜色。因此我们参考NOIP2010关押罪犯,维护并查集,对每个点\(x\)额外建一个点\(x'\)表示它的反集,在加边时,如果\(x,y\)位于同一集合,将不再是二分图。否则合并\(x,y'\)和\(y,x'\)。
但是边有存在时间,而并查集显然不能随意删边。这时候,线段树分治的一大妙用就派上用场了。
发现每个时刻都要询问是否为二分图,我们就对时间建立线段树。对于每条边,我们就按照区间加法的模式,把边挂在出现时间对应线段树内的\(\log\)个节点上,用链表实现。分治时,从根节点出发,每到一个节点,将挂在该节点上的所有边合并,然后递归处理左儿子和右儿子。如果发现有某条边合并会出现奇环,那么显然可以断言,当前线段树节点所对应的时间区间都不会形成二分图。当成功到达叶子节点的时候,我们惊奇地发现,在这一时刻的所有边已经在并查集中,而这一时刻也得到了Yes的肯定答案!
一口气搞完了线段树分治流程的一大半,现在我们看看之前不能随意删边的问题。发现我们遍历线段树过程的本质,实际上是跑了一遍dfn序。这明显是一个栈的过程,不断向下遍历,同时加边;再进行回溯,我们要删去的是最后加的边!而使用按秩合并不路径压缩的并查集,我们就可以轻松做到可撤销最后一步的加边。实现的时候,也要写一个栈,保存每次并查集合并的有关信息(合并时加入的边,合并后树高\(dep\)的变化),在线段树中处理完左右子树后,将在当前节点加入的边删除。
分治过程至此结束。来分析一下复杂度。每条边会挂在\(\log T\)个线段树节点上。因为并查集没有路径压缩,所以每次加边删边判连通性时都需要跳\(\log n\)次\(fa\)。于是得出了总复杂度\(m\log n\log T\)。
写法上,蒟蒻还通过许多Dalao的博客了解到另一种用异或和维护奇偶性的并查集写法。比起蒟蒻提到的这种,常数小了一半,但感觉思路没那么简洁(其实是因为我这种弱鸡弄不懂比不上Dalao)
实现细节方面,注意题目中时间段与时刻的区别(时间段从\(1\)开始,时刻从\(0\)开始)。数据中还有某些边出现了“瞬闪”的现象(出现时刻等于消失时刻),做区间加法时不特判一下会RE。数据中还有自环(不能构成二分图),不过使用蒟蒻这种并查集写法似乎没影响。
#include<cstdio>
#define I inline
#define RG register
#define R RG int
const int N=2e5,M=N<<1,L=4e6;
char buf[M],*pe=buf+M,*pp=pe-1;
int p,st[L],u[M],v[M],f[M],d[M],he[M],ne[L],id[L];
bool fl[L];
I void gc(){
if(++pp==pe)fread(pp=buf,1,M,stdin);
}
I void pc(RG char C){
if(++pp==pe)fwrite(pp=buf,1,M,stdout);*pp=C;
}
I int in(){
gc();while(*pp<'-')gc();
R x=*pp&15;gc();
while(*pp>'-')x=x*10+(*pp&15),gc();
return x;
}
I int get(R x){//直接跳fa
while(f[x])x=f[x];
return x;
}
I void merge(R x,R y){
if(x==y)return;
if(d[x]>d[y]){R t=x;x=y;y=t;}
f[st[++p]=x]=y;//按秩合并,信息压入栈
d[y]+=fl[p]=d[x]==d[y];//注意dep有没有变也要压
}
void upd(R t,R l,R r,R s,R e,R i){//区间加
if(l==s&&r==e){
ne[++p]=he[t];id[he[t]=p]=i;//挂链
return;
}
R m=(l+r)>>1;
if(e<=m)upd(t<<1,l,m,s,e,i);
else if(s>m)upd(t<<1|1,m+1,r,s,e,i);
else upd(t<<1,l,m,s,m,i),upd(t<<1|1,m+1,r,m+1,e,i);
}
void div(R t,R l,R r){
R x,y,i,m=(l+r)>>1,lst=p;
for(i=he[t];i;i=ne[i]){
if((x=get(u[id[i]]))==(y=get(v[id[i]]))){
for(;l<=r;++l)pc('N'),pc('o'),pc('\n');
goto E;//出现奇环,断定整个区间不合法
}
merge(get(u[id[i]]+N),y);//合并反点
merge(get(v[id[i]]+N),x);
}
if(l==r)pc('Y'),pc('e'),pc('s'),pc('\n');
else div(t<<1,l,m),div(t<<1|1,m+1,r);
E:for(;p>lst;--p)//撤销
d[f[st[p]]]-=fl[p],f[st[p]]=0;
}
int main(){//g++:unused variable ‘n’。蒟蒻:叫我怎么用你?
R n=in(),m=in(),t=in(),s,e;
for(R i=1;i<=m;++i){
u[i]=in();v[i]=in();s=in();e=in();
if(s!=e)upd(1,1,t,s+1,e,i);//特判
}
pp=buf-1;div(1,1,t);
fwrite(buf,1,pp-buf+1,stdout);
return 0;
}
例题二
BZOJ3237[Ahoi2013]连通图(点击进入题目)
这一个就没那么裸了,并没有体现任何询问的时间轴,以及修改对应的时间区间。
可是,一个图任意删边我们还是没法做,仍然要转化成加边。我们强行按照先后顺序对询问建时间轴,猛然发现,原来每条边的存在仍然是若干段时间区间!因为删边意味着由一段时间区间分裂成两段,所以总的段数控制在\(O(k)\)级别。
当然还是可以大力LCT。
当然接着使用线段树分治。这里的并查集使用带权的,维护好当前连通块大小。如果大小等于\(n\)说明图连通。
其它的部分代码几乎照搬。当然由删边的时间点转化为加边的时间区间需要写一个链表实现,蒟蒻使用了STL的list(又是第一次学一个STL。。。)
复杂度仍然是两个\(\log\),不过据说有随机权值的巧妙做法?
#include<cstdio>
#include<list>
#define I inline
#define RG register
#define R RG int
#define G c=getchar()
using namespace std;
const int N=2e5+9,M=4e5+9,L=8e6+9;
int n,p,st[M],f[N],s[N],d[N],a[M],b[M],he[M<<1],ne[L],id[L];
bool fl[M];
list<int>li[M];
I int in(){
RG char G;
while(c<'-')G;
R x=c&15;G;
while(c>'-')x*=10,x+=c&15,G;
return x;
}
I int get(R x){
while(f[x])x=f[x];
return x;
}
void upd(R t,R l,R r,R s,R e,R i){
if(l==s&&r==e){
ne[++p]=he[t];id[he[t]=p]=i;
return;
}
R m=(l+r)>>1;
if(e<=m)upd(t<<1,l,m,s,e,i);
else if(s>m)upd(t<<1|1,m+1,r,s,e,i);
else upd(t<<1,l,m,s,m,i),upd(t<<1|1,m+1,r,m+1,e,i);
}
void div(R t,R l,R r){
R i,x,y,m=(l+r)>>1,lst=p;
for(i=he[t];i;i=ne[i]){
if((x=get(a[id[i]]))==(y=get(b[id[i]])))continue;
if(s[x]+s[y]==n){//剪枝,对复杂度并没有什么优化
for(;l<=r;++l)puts("Connected");
goto E;
}
if(d[x]>d[y])swap(x,y);
s[f[st[++p]=x]=y]+=s[x];//带权合并
d[y]+=fl[p]=d[x]==d[y];
}
if(l==r)puts("Disconnected");
else div(t<<1,l,m),div(t<<1|1,m+1,r);
E:for(;p!=lst;--p)
d[f[x=st[p]]]-=fl[p],s[f[x]]-=s[x],f[x]=0;
}
int main(){
n=in();R m=in(),k,i,lst;
RG list<int>::iterator it;
for(i=1;i<=n;++i)s[i]=1;
for(i=1;i<=m;++i)
a[i]=in(),b[i]=in();
k=in();
for(i=1;i<=k;++i)
for(R c=in();c;--c)
li[in()].push_back(i);
for(i=1;i<=m;++i){
li[i].sort();li[i].push_back(k+1);//排个序
for(lst=1,it=li[i].begin();it!=li[i].end();++it){
if(lst!=*it)upd(1,1,k,lst,*it-1,i);
lst=*it+1;//把区间弄出来
}
}
p=0;div(1,1,k);
return 0;
}
线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)的更多相关文章
- 洛谷P4092 [HEOI2016/TJOI2016]树 并查集/树链剖分+线段树
正解:并查集/树链剖分+线段树 解题报告: 传送门 感觉并查集的那个方法挺妙的,,,刚好又要复习下树剖了,所以就写个题解好了QwQ 首先说下并查集的方法趴QwQ 首先离线,读入所有操作,然后dfs遍历 ...
- BZOJ-3211花神游历各国 并查集+树状数组
一开始想写线段树区间开方,简单暴力下,但觉得变成复杂度稍高,懒惰了,编了个复杂度简单的 3211: 花神游历各国 Time Limit: 5 Sec Memory Limit: 128 MB Subm ...
- BZOJ3211 花神游历各国 并查集 树状数组
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ3211 题意概括 有n个数形成一个序列. m次操作. 有两种,分别是: 1. 区间开根(取整) 2. ...
- 51 nod 1427 文明 (并查集 + 树的直径)
1427 文明 题目来源: CodeForces 基准时间限制:1.5 秒 空间限制:131072 KB 分值: 160 难度:6级算法题 安德鲁在玩一个叫“文明”的游戏.大妈正在帮助他. 这个游 ...
- hdu 6200 mustedge mustedge(并查集+树状数组 或者 LCT 缩点)
hdu 6200 mustedge mustedge(并查集+树状数组 或者 LCT 缩点) 题意: 给一张无向连通图,有两种操作 1 u v 加一条边(u,v) 2 u v 计算u到v路径上桥的个数 ...
- 【bzoj4869】[Shoi2017]相逢是问候 扩展欧拉定理+并查集+树状数组
题目描述 Informatik verbindet dich und mich. 信息将你我连结. B君希望以维护一个长度为n的数组,这个数组的下标为从1到n的正整数.一共有m个操作,可以分为两种:0 ...
- 并查集+树链剖分+线段树 HDOJ 5458 Stability(稳定性)
题目链接 题意: 有n个点m条边的无向图,有环还有重边,a到b的稳定性的定义是有多少条边,单独删去会使a和b不连通.有两种操作: 1. 删去a到b的一条边 2. 询问a到b的稳定性 思路: 首先删边考 ...
- 2019西北工业大学程序设计创新实践基地春季选拔赛 I Chino with Rewrite (并查集+树链剖分+线段树)
链接:https://ac.nowcoder.com/acm/contest/553/I 思路:离线整棵树,用并查集维护下联通的情况,因为值只有60个,用2的x(1<=x<=60)次方表示 ...
- Hihocoder1883 : 生成树问题(并查集+树剖+线段树)
描述 有一个无向图,有n个点,m1条第一类边和m2条第二类边.第一类边有边权,第二类边无边权.请为第二类的每条边定义一个边权,使得第二类边可能全部出现在该无向图的最小生成树上,同时要求第二类边的边权总 ...
- 【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树的直径/LCA/树的点分治
题目描述 农夫约翰有N(2≤N≤40000)个农场,标号1到N,M(2≤M≤40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样, 图中农场用F ...
随机推荐
- Flask源码解读--所有可扩展点
一.前言 flask中有很多可扩展点(笔者这样称呼),其中包含了信号和请求钩子,这些信号和钩子有什么用呢?其主要作用用于帮助我们进行程序的耦合性,当然还可以让我们自定义一些行为.话不多说,通过阅读源码 ...
- fastjson tojson部分规则
fastjson 作为java 目前最快速,最轻便 json对象,与json 字符串转换 第三方包,阿里巴巴提供. 对象转json规则 转json字符串 列 JSONObject.toJSON(ne ...
- kafka学习2:kafka集群安装与配置
在前一篇:kafka学习1:kafka安装 中,我们安装了单机版的Kafka,而在实际应用中,不可能是单机版的应用,必定是以集群的方式出现.本篇介绍Kafka集群的安装过程: 一.准备工作 1.开通Z ...
- BootStrap学习(3)_导航菜单
一.导航元素 1.表格导航或标签 以一个带有 class .nav 的无序列表开始. 添加 class .nav-tabs. <!DOCTYPE html> <html xmlns= ...
- Centos下DNS+NamedManager高可用部署方案完整记录
之前说到了NamedManager单机版的配置,下面说下DNS+NamedManager双机高可用的配置方案: 1)机器环境 主机名 ip地址 dns01.kevin.cn 192.168.10.20 ...
- 利用阿里云的源yum方式安装Mongodb
今天在线上服务器上安装MongoDB,从Mongo官网直接下载链接,结果在下载时发觉速度慢的可怜.迫于无奈,只能找国内的镜像下载.这里选择阿里云的源进行安装,记录如下: 1)在/etc/yum.rep ...
- beta阶段测试基本概况报告
文件地址 测试基本信息 Bitmap 测试 ...
- M2事后总结
照片 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? "北航"Clubs旨在于解决北航校内社团管理与学生参与社团活动的困难的 ...
- 读书笔记(chapter3)
进程管理 3.1进程 1.进程:进程就是处于执行期的程序,实际上,进程就是正在执行的程序代码的实时结果: 2.执行线程,简称线程,是进程中活动的对象(每个线程拥有独立的程序计数器.进程栈.和一组进程寄 ...
- js/jquery禁止页面回退
$(function() { //防止页面后退 history.pushState(null, null, document.URL); window.addEventListener('popsta ...