BZOJ4025 二分图 分治 并查集 二分图 带权并查集按秩合并
原文链接http://www.cnblogs.com/zhouzhendong/p/8683831.html
题目传送门 - BZOJ4025
题意
有$n$个点,有$m$条边。有$T$个时间段。其中第$i$条边连接节点$x_i,y_i$,并且在$start_i$时刻出现,在$end_i$时刻消失。问每一个时刻的图是不是二分图。
$n\leq 10^5,m\leq 2\times 10^5,T\leq 10^5$
题解
真是一道好题。
做这题我才发现我从来没写过按秩合并的并查集QAQ。
先考虑按照时间二分。
对于某一段时间,我们可以把所有在当前时间段一直出现的边连起来。这个可以用按秩合并的带权并查集维护。(注意子程序退出的时候要撤销所有操作)
如果在加边的过程中,发现冲突,那么该区间全部都是NO了。
否则把除了完全覆盖当前区间的边之外的,对左区间有关的扔到左边,对右区间有关的扔到右边。然后递归子区间处理。
注意区间长度为1的时候不要再递归下去了,会RE的。
具体实现参见代码。
我们来分析一下为什么复杂度是对的。
首先考虑空间复杂度。
考虑每一层递归的时候最多有$O(n)$条边,最多有$O(\log n)$层,所以空间复杂度为$O(n\log n)$。
考虑时间复杂度。
我们发现主要的复杂度在边的处理上。一条边在多少个时间段被连接,就是他对总时间复杂度的贡献。
显然每一条边在同一个区间只会被连接一下,但是要按秩合并并查集,所以单次复杂度为$O(\log n)$。考虑到一个边最多在$O(\log n)$个区间被连接(和线段树区间覆盖的原理差不多吧)。所以每一条边最多贡献$O(\log^2 n)$的时间复杂度。所以有$m$条边,显然$m$的上限和$n$同阶,当他是$n$就可以了,所以总的时间复杂度为$O(n\log^2 n)$。
代码
#include <bits/stdc++.h> using namespace std; const int N=200005; struct Edge{ int x,y,s,t; void get(){ scanf("%d%d%d%d",&x,&y,&s,&t),s++; } }e[N]; int n,m,T,ans[N]; vector <int> x; struct UFset{ int n,fa[N],depth[N],d[N],stack[N],top; void init(int _n){ n=_n; for (int i=1;i<=n;i++) fa[i]=i; memset(depth,0,sizeof depth); memset(d,0,sizeof d); top=0; } int getf(int x){ while (fa[x]!=x) x=fa[x]; return x; } int getdis(int x){ int ans=0; while (fa[x]!=x) ans^=d[x],x=fa[x]; return ans; } bool Merge(int x,int y){ int D=getdis(x)^getdis(y)^1; x=getf(x),y=getf(y); if (x==y) return D==0; if (depth[x]<depth[y]) swap(x,y); if (depth[x]==depth[y]) depth[x]++,stack[++top]=-x; fa[y]=x,d[y]=D,stack[++top]=y; return 1; } void Split(int time){ while (top>time){ int x=stack[top--]; if (x<0) depth[-x]--; else fa[x]=x,d[x]=0; } } }s; void solve(int L,int R,vector <int> &now){ if (now.size()==0) return; vector <int> Lpart,Rpart; Lpart.clear(),Rpart.clear(); int time=s.top,mid=(L+R)>>1; for (int i=0;i<now.size();i++){ int id=now[i]; if (e[id].s<=L&&e[id].t>=R){ if (!s.Merge(e[id].x,e[id].y)){ for (int j=L;j<=R;j++) ans[j]=0; s.Split(time); return; } } else { if (e[id].t<=mid) Lpart.push_back(id); else if (e[id].s>mid) Rpart.push_back(id); else if (e[id].s<=mid&&e[id].t>mid) Lpart.push_back(id),Rpart.push_back(id); } } if (L==R){ s.Split(time); return; } solve(L,mid,Lpart); solve(mid+1,R,Rpart); s.Split(time); } int main(){ scanf("%d%d%d",&n,&m,&T); x.clear(); for (int i=1;i<=m;i++) e[i].get(),x.push_back(i); for (int i=1;i<=T;i++) ans[i]=1; s.init(n); solve(1,T,x); for (int i=1;i<=T;i++) puts(ans[i]?"Yes":"No"); return 0; }