BZOJ4025 二分图(线段树分治+可撤销带权并查集)
根据数据是区间,也是时间,应该可以想到时间线段树分治
问题就是如何查询二分图。
一般判定二分图就是判断是否存在奇数环。
那么奇数环就是当我们加入一条边,如果他们本身是联通的并且距离为偶数,这样就是奇数,并且一旦找到奇数环,下面的时间永远是奇数环
因此我们可以用带权并查集来维护距离信息,这是经典套路,本体距离只有奇偶,因此直接异或判断。
但是我们回溯的时候需要撤销并查集,因此不能路径压缩,只能采用按秩合并的思路,并且记录栈,按插入的顺序再撤销他
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; struct node{ int l,r; vector<pll> num; }tr[N<<2]; struct seg{ int x,y,f,mx,d; }; int n,m,T; int p[N],sz[N],d[N]; stack<seg> s; int ans[N]; int find(int x){ if(p[x]!=x){ return find(p[x]); } return x; } void build(int u,int l,int r){ if(l==r){ tr[u]={l,r}; } else{ tr[u]={l,r}; int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); } } void modify(int u,int l,int r,pll x){ if(tr[u].l>=l&&tr[u].r<=r){ tr[u].num.push_back(x); return ; } int mid=tr[u].l+tr[u].r>>1; if(l<=mid) modify(u<<1,l,r,x); if(r>mid) modify(u<<1|1,l,r,x); } void init(){ int i; for(i=1;i<=n;i++){ p[i]=i,d[i]=0,sz[i]=1; } } int getdis(int x){ if(x!=p[x]){ return (d[x]^getdis(p[x])); } return d[x]; } void solve(int u){ int i; int flag=0; int cnt=0; for(i=0;i<(int)tr[u].num.size();i++){ int x=tr[u].num[i].first,y=tr[u].num[i].second; int pa=find(x),pb=find(y); if(pa==pb){ int a=getdis(x); int b=getdis(y); if(a==b){ flag=1; break; } } else{ if(sz[pa]>sz[pb]){ swap(x,y); swap(pa,pb); } int tmp=p[pa]; p[pa]=pb; d[pa]=d[x]^d[y]^1; s.push({pa,pb,tmp,sz[pb],0}); sz[pb]+=sz[pa]; cnt++; } } if(!flag){ if(tr[u].l==tr[u].r){ ans[tr[u].l]=1; } else{ int mid=tr[u].l+tr[u].r>>1; solve(u<<1); solve(u<<1|1); } } while(cnt--){ auto tmp=s.top(); s.pop(); p[tmp.x]=tmp.f; sz[tmp.y]=tmp.mx; d[tmp.x]=tmp.d; } } int main(){ ios::sync_with_stdio(false); cin>>n>>m>>T; int i; build(1,1,T); for(i=1;i<=m;i++){ int a,b,c,d; cin>>a>>b>>c>>d; modify(1,c+1,d,{a,b}); } init(); solve(1); for(i=1;i<=T;i++){ if(ans[i]){ cout<<"Yes"<<endl; } else{ cout<<"No"<<endl; } } return 0; }
没有人不辛苦,只有人不喊疼