【题意】:给你一个图,问删除一个节点后,该图还是不是二分图
【题解】:题意很简单,显然数据范围不可能允许我们n方暴力。关于判断二分图我们可以用染色法,也可以使用并查集,染色法每次都需要遍历全图,显然是行不通的,我们这题采用的是cdq分治+并查集,这里的并查集要求不能路径压缩,并且可删除,关于并查集的可删除我们可以每次用一个栈记录新合并之前的节点的状态,还原的时候把栈中储存的信息一个一个拿出来就好。
主要考虑cdq分治对这题目的优化,我们考虑计算删除a节点之后和删除b节点之后,显然不考虑特殊情况,在这两张图栈中,绝大多数的边都还是一样的,只有连接a和b的少部分边是不同的,所以在这道题目中我们可以用cdq分治来优化并查集的合并过程(二分图染色过程).我们考虑solve(l,r),我们把这个分为两个过程solve(l,mid)solve(mid+1,r),这两个过程是类似的,并且这里其实并没有合并答案的操作。
所以我们就拿solve(l,mid)来说,显然我们把solve(l,mid)公共需要加上的边加上就能进行优化,这时我们把所有两端点都不在[l,mid]区间内的边都进行合并操作,显然无论删除[l,mid]中的哪个节点,这样的合并操作都不会有什么问题,所以我们一直重复这样的操作直到l==r,这样我们就可以在合适的复杂度解决该问题了
细节见代码,
#include<set> #include<map> #include<cmath> #include<stack> #include<queue> #include<bitset> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define PB push_back #define MP make_pair #define ll __int64 #define MS(a,b) memset(a,b,sizeof(a)) #define LL (rt<<1) #define RR (rt<<1|1) #define lson l,mid,LL #define rson mid+1,r,RR #define pii pair<int,int> #define lowbit (x&(-x)) using namespace std; const int MAXN=1e5+10; const int inf=0x3f3f3f3f; const int mod=1e9+7; vector<int>G[MAXN]; int fa[MAXN],col[MAXN],rk[MAXN],ans[MAXN]; int tot,top; struct node { int u,v,colu,colv,rku,rkv,fau,fav; node(){} node(int _u,int _v,int _colu,int _colv,int _rku,int _rkv,int _fau,int _fav):u(_u),v(_v),colu(_colu),colv(_colv),rku(_rku),rkv(_rkv),fau(_fau),fav(_fav){} }S[MAXN]; void init(int n) { for(int i=1;i<=n;i++)G[i].clear(); tot=top=0; for(int i=0;i<n+10;i++)fa[i]=i,col[i]=1,rk[i]=1; } int find_fa(int x) { int o=x; while(fa[o]!=o)o=fa[o]; return o; } int find_col(int x) { if(fa[x]==x)return col[x]; if(!col[x]) return !find_col(fa[x]); return find_col(fa[x]); } bool merge(int u,int v) { int a=find_fa(u),b=find_fa(v);//ab的根 int x=find_col(u),y=find_col(v);//uv的颜色 int root,next; if(a==b){//如果同根 if(x==y)return false;//同色 他们之间又有现在这条边 说明改图不是二分图 return true; } if(rk[a]>rk[b])root=a,next=b;//并查集优化操作 else root=b,next=a; S[top++]=node(a,b,col[a],col[b],rk[a],rk[b],fa[a],fa[b]);//将当前信息存入栈中 以便还原并查集 if(x==y&&col[root]==1)col[next]^=1;//如果uv根节点颜色相同,为了合并 将其中一个改变颜色 fa[next]=root; rk[root]+=rk[next]; return true;//成功合并,当前并查集集合中的节点构成的是一个二分图 } bool unite(int l,int r,int a,int b) { bool flag=true; for(int u=l,v;u<=r;u++){ for(int i=0;i<G[u].size();i++){ v=G[u][i]; if(a<=v&&v<=b)continue;//如果v在对立区间中跳过 if(!merge(u,v))flag=false;//如果merge不成功 } } return flag; } void back(int x) { node tmp; while(top>x){ --top; tmp=S[top]; int u=tmp.u,v=tmp.v; rk[u]=tmp.rku;rk[v]=tmp.rkv; fa[u]=tmp.fau;fa[v]=tmp.fav; col[u]=tmp.colu;col[v]=tmp.colv; } } void cdq(int l,int r,bool flag) { if(l==r){ans[l]=flag;return;}//根据之前的操作这里已经把所有边都加上了 除了和l有关的边 也就相当于删除了l这个节点,在之前的合并操作过程中就是一个判断二分图的过程 int mid=(l+r)>>1; int pre=top; bool now=flag&&unite(mid+1,r,l,mid);//把端点不跨越[l,mid] [mid+1,r]两个区间的边都连上 和之前操作合并起来就是把所有无关区间[l,mid]中节点的边都连上 cdq(l,mid,now); back(pre);//删除之前的并查集操作 now=flag&&unite(l,mid,mid+1,r);//这里和上面一样 cdq(mid+1,r,now); back(pre); } int main() { int T,n,m; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); init(n);//初始化 for(int i=0,u,v;i<m;i++){//建图 scanf("%d%d",&u,&v); G[u].PB(v);G[v].PB(u); } cdq(1,n,true);//运算 for(int i=1;i<=n;i++)printf("%d",ans[i]); puts("");//输出答案 } return 0; }