二分图就是一个节点数>=2的图如果能被分成左右非空的两部分,且同一集合内部的边不相连,那么就是一个二分图.一张图是二分图,当且仅当图中不存在点数为奇数的环.因为链都好说,而环内相邻的点一定都不在同一个部分,但是点数为奇数时就会有点不能找到自己的位置...
因为两个部分是等价的,对于一个图直接从一个节点出发dfs即可,复杂度n+m.
using namespace std; bool dfs(int now){ int tv=v[now]; for(int j=head[now];j;j=e[j].next){ if(v[e[j].y]) if(v[e[j].y]+tv==3) continue; else return 0; else{ v[e[j].y]=3%tv; if(!dfs(e[j].y))return 0; } } return 1; } int main() { init(); memset(v,0,sizeof(v)); for(i=1;i<=n;i++) { if(!v[i]) { v[i]=1; if(!dfs(i)) break; } } if(i==n+1) cout<<"yes"; else cout<<"no"; }
那么来看一个题吧:
并查集大概用的也是二分图的思想,考虑如何只用判断二分图的思想来做这道题.
考虑到答案具有单调性,答案ans及更大的影响力都是可以达到的,更低的影响力则不能达到.那么可以开始二分答案了,其中的check()函数检查影响力最大为多少多少时能否构成二分图.具体实现只需要可以在那个dfs函数里向旁边dfs的时候加一句边长与当前边的比较即可.复杂度(m+n)log21e9.
using namespace std; int i,xx,yy,vv; int n,m,tot,head[20010],v[20010]; int l,r,mid; struct edge{ int x,y,v; int next; }e[200010]; void add(int x,int y,int v){ tot++; e[tot].x=x; e[tot].y=y; e[tot].v=v; e[tot].next=head[x]; head[x]=tot; return ; } bool dfs(int now,int vnow){ int tv=v[now]; for(int j=head[now];j;j=e[j].next){ if(e[j].v<=vnow)continue; if(v[e[j].y]) if(v[e[j].y]+tv==3) continue; else return 0; else{ v[e[j].y]=3-tv; if(!dfs(e[j].y,vnow))return 0; } } return 1; } bool check(int now){ memset(v,0,sizeof(v)); for(i=1;i<=n;i++){ if(!v[i]){ v[i]=1; if(!dfs(i,now))return 0; } } return 1; } int main(){ n=read();m=read(); for(i=1;i<=m;i++){ xx=read();yy=read();vv=read(); add(xx,yy,vv); add(yy,xx,vv); r=max(r,vv); } while(l+1!=r){ mid=(l+r)>>1; if(check(mid)) r=mid; else l=mid; } if(check(l))cout<<l; else cout<<r; return 0; }
虽然看起来没有并查集优秀,但是总时间却比并查集算法快...
"任意两条边都没有公共端点"的边的集合被称为图的一组匹配,在二分图中,包含边数最多的一组匹配被称为二分图的最大匹配.(算法竞赛进阶指南p394).
由于性质:二分图的一组匹配是最大匹配当且仅当图中不存在增广路.这样就可以用匈牙利算法(增广路算法)求一个二分图的最大匹配了.复杂度(MN).
bool dfs(int now){ for(int j=head[now];j;j=e[j].next){ if(!v[e[j].y]){ v[e[j].y]=1; if(!match[e[j].y]||dfs(match[e[j].y])){ match[e[j].y]=now; return 1; } } } return 0; } int main() { init(); for(i=n;i<=n*n+n-1;i++){ memset(v,0,sizeof(v)); if(dfs(i))ans++; } }
看一道题:来自http://www.tyvj.cn/p/1035
再来一个样例吧:
4 6 1 3 1 4 2 1 2 3 4 2 4 4
再画一下图:
青色是被禁止放置的点,红色是要放骨牌的地方.可以看出来答案是5.首先建图,然后跑一下增广路即可.匈牙利具体是怎么跑的呢?它每次从一个点进入dfs看相连的节点有没有还没有被匹配的点.如果有就可以利用它进行增广,并且返回1,或者去dfs相邻的点,如果它返回了1也可以用它进行增广.
using namespace std; int i,f; int n,sum,ans,match[1111];//最大为n*n+n-1 struct edge{ int x,y; int next; }e[80010];//点数*方向数*双向n^2*4*2 int tot,head[1111],v[1111],map[1111][1111]; void add(int x,int y){ tot++; e[tot].x=x; e[tot].y=y; e[tot].next=head[x]; head[x]=tot; tot++; e[tot].x=y; e[tot].y=x; e[tot].next=head[y]; head[y]=tot; return ; } bool dfs(int now){ for(int j=head[now];j;j=e[j].next){ if(!v[e[j].y]){ v[e[j].y]=1; if(!match[e[j].y]||dfs(match[e[j].y])){ match[e[j].y]=now; return 1; } } } return 0; } int main(){ n=read();sum=read(); for(;sum;sum--) map[read()][read()]=1;//如果你include<map>或者万能库,map就是关键字 for(i=1;i<=n;i++){//建图 for(f=1;f<=n;f++){ if(map[i][f])continue; if(!map[i][f-1]&&f!=1) add(i*n+f-1,i*n+f-2); if(!map[i][f+1]&&f!=n) add(i*n+f-1,i*n+f); if(!map[i-1][f]&&i!=1) add(i*n+f-1,i*n+f-1-n); if(!map[i+1][f]&&i!=n) add(i*n+f-1,i*n+f-1+n); } } for(i=n;i<=n*n+n-1;i++){//跑匈牙利 memset(v,0,sizeof(v)); if(dfs(i))ans++; } cout<<ans/2;//ans是最大匹配中的点数,而每两个点能放一个骨牌 return 0; }