P5787 线段树分治
https://www.luogu.com.cn/problem/P5787
同BZOJ4025
\(n\) 个点的图,\(m\) 条边分别在各自的 \([l,r]\) 时间段内会出现,对于 \([1,k]\) 内每个时间点求这个图是否是二分图
用到了线段树分治,就是以时间为下标建立一个线段树,然后根据线段树的区间操作,每个时间段 \([l,r]\) 可以分成 \(O(\log k)\) 个区间(时间区间),然后对这些区间做上标记,最后 dfs 线段树,线段树上的根节点对应的就是一个时间点,想判断这个时间点是不是二分图只要把它所有父亲的标记都考虑上就行了
也就是每进入一个线段树节点,就把这个节点上记录的所有边都加入图,如果出现奇环(二分图等价于无奇环),这个区间内所有时间点都是不是,否则就向下继续 dfs,离开节点是再把这些边删掉
要维护奇环,还得用带撤销的并查集,把每个图中的点拆成两个点(\(i\) 和 \(i+n\) 来表示),如果加入边 \((u,v)\),\(u,v\) 在同一集合那么就不是二分图,否则就在并查集中合并 \((u,v+n),(v,u+n)\) 所在的集合
可以理解为二分图就是要求把点分成两类,而 \(i,i+n\) 分别代表 \(i\) 属于第一类还是第二类。把要成立必须同时成立的并查集中的点放入一个集合
边必须在不同类的两个点之间,所以有了如上的连边(并查集中连)规则;同理,如果 \(u,v\) 已经在同一集合,它们必须同时成立,也就是同时属于一类,那么出现矛盾
为了支持撤销,其实不用可持久化什么的,只要来一个栈维护每次更改并查集数组的信息就行了,然后一步一步回退
不过因此也就不能用路径压缩了,需要按大小合并
我居然把并查集写错导致WA了好久
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<vector>
#include<cstring>
#define reg register
#define EN puts("")
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
#define N 200006
int n,m,k;
struct Edge{
int u,v;
};
struct Node{
Node *ls,*rs;
std::vector<Edge>edge;
}dizhi[N*2],*root=&dizhi[0];
int tot;
void build(Node *tree,int l,int r){
if(l==r) return;
tree->ls=&dizhi[++tot];tree->rs=&dizhi[++tot];
int mid=(l+r)>>1;
build(tree->ls,l,mid);build(tree->rs,mid+1,r);
}
void insert(Node *tree,int l,int r,int ql,int qr,Edge e){
if(ql<=l and r<=qr) return tree->edge.push_back(e),void();
int mid=(l+r)>>1;
if(ql<=mid) insert(tree->ls,l,mid,ql,qr,e);
if(qr>mid) insert(tree->rs,mid+1,r,ql,qr,e);
}
int stack[N*4],top;
int size[N*2],fa[N*2];
inline int find(int k){return k==fa[k]?k:find(fa[k]);}
inline void merge(int x,int y){
if(x==y) return;
if(size[x]>size[y]) x^=y,y^=x,x^=y;
stack[++top]=x;stack[++top]=size[y];
fa[x]=y;size[y]+=size[x];
}
void work(Node *tree,int l,int r){
int edge_size=tree->edge.size(),last_top=top;
for(reg int i=0,u,v,rootu,rootv;i<edge_size;i++){
u=tree->edge[i].u;v=tree->edge[i].v;
rootu=find(u);rootv=find(v);
if(rootu==rootv){
for(reg int j=l;j<=r;j++) puts("No");
goto NO;
}
merge(find(u+n),rootv);merge(find(v+n),rootu);
}
if(l==r) puts("Yes");
else{
int mid=(l+r)>>1;
work(tree->ls,l,mid);work(tree->rs,mid+1,r);
}
NO:;
while(top>last_top){
size[fa[stack[top-1]]]=stack[top];
fa[stack[top-1]]=stack[top-1];
top-=2;
}
}
int main(){
n=read();m=read();k=read();
build(root,1,k);
for(reg int i=1;i<=n;i++) fa[i]=i,fa[i+n]=i+n,size[i]=size[i+n]=1;
reg int u,v,l,r;
while(m--){
u=read();v=read();l=read();r=read();
if(l^r) insert(root,1,k,l+1,r,(Edge){u,v});
}
work(root,1,k);
return 0;
}