线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话

stO猫锟学长,满脑子神仙DS

线段树分治思想

我们在做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;
}

原文地址:https://www.cnblogs.com/flashhu/p/9404225.html

时间: 2024-08-26 12:13:40

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)的相关文章

【bzoj3211】花神游历各国 并查集+树状数组

原文地址:http://www.cnblogs.com/GXZlegend/p/6809714.html 题目描述 输入 输出 每次x=1时,每行一个整数,表示这次旅行的开心度 样例输入 4 1 100 5 5 5 1 1 2 2 1 2 1 1 2 2 2 3 1 1 4 样例输出 101 11 11 题解 并查集+树状数组,附带一点数学知识 因为√1=1,且一个数x开接近log2(log2x)次平方后就会变成1,这个数是非常小的. 所以我们完全可以暴力修改,只需要知道每次的修改区间中有多少真

51 nod 1427 文明 (并查集 + 树的直径)

1427 文明 题目来源: CodeForces 基准时间限制:1.5 秒 空间限制:131072 KB 分值: 160 难度:6级算法题 安德鲁在玩一个叫“文明”的游戏.大妈正在帮助他. 这个游戏里面有n个城市和m条双向的道路.城市从1到n编号.对于每一对城市,他们之间要么有唯一的一条道路,要么就是不可互达.一条道路的定义是一个包含不同城市的序列 v1, v2,...,vk ,  vi  和  vi+1 (1≤ i < k)之间有直接的一条道路相连.这条道路的长度是k-1.两个城市在同一区域的

BZOJ 3038 上帝造题的七分钟2 (并查集+树状数组)

题解:同 BZOJ 3211 花神游历各国,需要注意的是需要开long long,还有左右节点需要注意一下. #include <cstdio> #include <cmath> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; LL a[100005],c[100005]; int f[100005],n,m,op,l,r,t; int s

POJ2985 The k-th Largest Group[树状数组求第k大值 并查集]

The k-th Largest Group Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 8807   Accepted: 2875 Description Newman likes playing with cats. He possesses lots of cats in his home. Because the number of cats is really huge, Newman wants to g

Hdu 5458 Stability (LCA + 并查集 + 树状数组 + 缩点)

题目链接: Hdu 5458 Stability 题目描述: 给出一个还有环和重边的图G,对图G有两种操作: 1 u v, 删除u与v之间的一天边 (保证这个边一定存在) 2 u v, 查询u到v的路径上有几条桥. 解题思路: 这个题目有很多次操作,包含查询和删边两类,首先想到的是连通分量加缩点.如果按照顺序来,删边时候求桥就是问题了.所以可以离线处理,然后一边记录答案一边加边缩点. 对于一个图,把连通分量缩成一个点后,这个图就成为了一棵树, 然后深度差就等于桥的数目.查询的时候对于(u, v)

HDU 5458 Stability(双连通分量+LCA+并查集+树状数组)(2015 ACM/ICPC Asia Regional Shenyang Online)

题目大意:给一个N个点M条边的无向图,有Q个询问:1.删掉a.b之间所存在的边:2.询问有多少条边,单独删掉之后a与b不再连通. 思路:脑洞大开. 对于询问,首先想到的就是a与b之间有多少桥(割边),然后想到双连通分量,然而删边是个坑爹的问题,于是我们离线倒着来,把删边变成加边. 双连通分量这种东西呢,其实缩点连起来之后,就是一棵树辣. 然后询问两个点的时候,设根到点x的距离为dep[x],a.b的最近公共祖先为lca(a, b),那么询问query(a, b) = dep[a] + dep[b

数据结构(并查集||树链剖分):HEOI 2016 tree

[注意事项] 为了体现增强版,题目限制和数据范围有所增强: 时间限制:1.5s 内存限制:128MB 对于15% 的数据,1<=N,Q<=1000. 对于35% 的数据,1<=N,Q<=10000. 对于50% 的数据,1<=N,Q<=100000,且数据均为官方数据. 对于100% 的数据,1<=N,Q<=1000000. 请注意常数因子对于程序运行的影响. 并查集很简单,并查集就是倒序处理,表示删除一个点的标记,删除后不会再加回来,删完后,合并当前点与其

[BZOJ 3211]花神游历各国(并查集+树状数组)

Description Solution 树状数组单点修改区间查询 我们知道一个数n最多修改loglogn次就会变为1 并查集维护每个数右边第一个不为1的位置 #include<cstdio> #include<iostream> #include<cstdlib> #include<cstring> #include<cmath> #define MAXN 100005 using namespace std; typedef long lon

并查集树数据结构hdu1325

我的解法就是去构造了一棵树 以数组的存储方式 数组的值存放节点的根. 排除空树 剩下的就是出现环和多根节点的情况 也就是排除森林和有一个节点多个入度的情况 排除森林就用到了并查集 也就是便利数组让其仅仅有一个根 排除多个入度的情况更简单 就是把这个点插入到数上时 假设这个点已经有了根节点,就出现了两个入度 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int set

ZOJ3761(并查集+树的遍历)

Easy billiards Time Limit: 2 Seconds      Memory Limit: 65536 KB      Special Judge Edward think a game of billiards is too long and boring. So he invented a new game called Easy billiards. Easy billiards has N balls on a brimless rectangular table i