[bzoj4025] 二分图
题意:
有一张n个点的图,有m条边,每条边只在$[l,r]$时段内存在。
请你对每个时刻求出此时这张图是否是二分图。
$n,k\leq 10^{5},m\leq 2\times 10^{5}$。
题解:
首先二分图的充要条件是没有奇环。(不一定非得连通)
那么有一个套路的带权并查集判二分图的做法(不考虑时间的限制):
按任意顺序加边,对于一条边$(u,v)$:
- 如果u,v不连通,直接加入。
- 否则,求一下u到v路径长度的奇偶性:
- 如果是偶数那么加了这条边形成了一个奇环,所以这张图已经是二分图了。
- 如果是奇数那么可以忽略这条边,因为用当前路径$u\rightarrow v$可以在任何一个环中替代边$(u,v)$。(重复路径不影响环的奇偶性)
用带权并查集维护每个点在并查集里的父亲、它到父亲路径长度的奇偶性。
注意在合并u,v的时候我们实际上合并的是$f_{u},f_{v}$,而这两个点之间的路径应该是$f_{u}\rightarrow u\rightarrow v\rightarrow f_{v}$,所以更新路径长度时需要暴力从u,v往上跳。
回到这道题,我们按线段树分治的经典套路,把操作挂到线段树节点上。
本来可以路径压缩,但离开一个节点时需要撤销操作,于是不能破坏父子关系,只能按秩合并(把siz小的合并到siz大的上)。
复杂度$O(n\log^{2}{n})$,用LCT可以做到$O(n\log{n})$,不过……
套路:
- 判二分图$\rightarrow$黑白染色(适用面较窄)或带权并查集。
- 支持时间区间上的可撤销操作和查询某时刻的状态$\rightarrow$线段树分治。
代码:
#include<bits/stdc++.h> #define maxn 100005 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define rint register int #define mp make_pair #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; vector<pair<int,int> > tr[maxn<<2],ok[maxn<<2]; int n,m,k,F[maxn],D[maxn],ans[maxn],siz[maxn]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline int getD(int x){return F[x]==x?0:D[x]^getD(F[x]);} inline int find(int x){return F[x]==x?x:find(F[x]);} inline void upd(int u,int v,int x,int y,int l,int r,int k){ if(x<=l && r<=y){tr[k].push_back(mp(u,v));return;} int mid=l+r>>1; if(x<=mid) upd(u,v,x,y,l,mid,k<<1); if(y>mid) upd(u,v,x,y,mid+1,r,k<<1|1); } inline void solve(int l,int r,int op,int k){ if(op){ for(int i=0;i<tr[k].size();i++){ int u=tr[k][i].first,v=tr[k][i].second; int t1=find(u),t2=find(v); if(t1!=t2){ if(siz[t1]<siz[t2]) swap(u,v),swap(t1,t2); int d1=getD(u),d2=getD(v); ok[k].push_back(mp(t1,t2)); F[t2]=t1,D[t2]=d1^d2^1,siz[t1]+=siz[t2]; } else if(!(getD(u)^getD(v))) op=0; } } if(l==r) ans[l]=op; int mid=l+r>>1; if(l!=r) solve(l,mid,op,k<<1),solve(mid+1,r,op,k<<1|1); for(int i=ok[k].size()-1;i>=0;i--){ int t1=ok[k][i].first,t2=ok[k][i].second; F[t2]=t2,D[t2]=0,siz[t1]-=siz[t2]; } } int main(){ n=read(),m=read(),k=read(); for(int i=1;i<=m;i++){ int u=read(),v=read(),l=read()+1,r=read(); upd(u,v,l,r,1,k,1); } for(int i=1;i<=n;i++) F[i]=i,D[i]=0,siz[i]=1; solve(1,n,1,1); for(int i=1;i<=k;i++){ if(ans[i]) printf("Yes\n"); else printf("No\n"); } return 0; }