二分图相关

mac2022-06-30  24

概念

对于一个图\(G=(V,E)\),若能将其点集分为两个互不相交的两个子集\(X、Y\), 使得\(X∩Y=\phi\),且对于\(G\)的边集\(V\),若其所有边的顶点全部一侧属于\(X\), 一侧属于\(Y\),则称图\(G\)为一个二分图。

定理

当且仅当无向图\(G\)的回路个数为偶数时,图\(G\)为一个二分图。 无回路的图也是二分图。

判定

在二分图\(G\)中,任选一个点\(V\), 使用\(BFS\)预处理出其他点相对于\(V\)的距离(边权为\(1\)) 对于每一条边\(E\),枚举它的两个端点,若其两个端点的值, 一个为奇数,一个为偶数,则图\(G\)为一个二分图。

匹配

对于一个二分图\(G\)的子图\(M\)(前提是在二分图中),若\(M\)的边集\(E\)的的任意两条边都不连接同一个顶点, 则称\(M\)\(G\)的一个匹配。特别的,其中边数最多的\(M\)称为二分图的最大匹配。

匈牙利算法

一、算法描述

建立有向图\(G\),分为二分图的左侧和右侧。 优先选择左侧序号更小的连接可能的边。 对于两个点的目标点“冲突”的时候,采取“协商”的办法。 即序号小的连接可能连接的另一条边。 若“协商”失败,则放弃序号较大的点的边。

二、步骤

推荐看这篇大佬的博客千古神犇zay 以上内容基本来自这里。

三、总结

\(1)\)如果后来的和以前的发生矛盾,则以前的优先退让。\(2)\)如果以前的退让之后自己无法匹配,则以前的拒绝退让,新来的去寻找下一个匹配。\(3)\)如果新来的谁也匹配不上了,那就这么单着吧。 所以说做法已经很清晰了,直接上代码吧。

\(Code\)

//二分图匹配-匈牙利算法 1000ms #include<cstdio> #include<iostream> #include<algorithm> #include<cstring> #define maxn 2005 #define re register using namespace std; int n,m,es,x,y,cnt,head[maxn]; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int ans,used[maxn]; bool vis[maxn]; struct Edge{ int v,nxt; }e[maxn*maxn]; inline void add(int u,int v) { e[++cnt].v=v; e[cnt].nxt=head[u]; head[u]=cnt; } bool dfs(int x) { for(int i=head[x];i;i=e[i].nxt) { int ev=e[i].v; if(!vis[ev]) { vis[ev]=true; if(used[ev]==0||dfs(used[ev]))//要注意这里dfs的是used[ev]而不是ev { used[ev]=x; return true; } } } return false;//注意这里返回false,否则会TLE } void Maxmatch() { for(re int i=1;i<=n;++i) { memset(vis,false,sizeof(vis)); ans+=dfs(i); } } int main() { n=read(),m=read(),es=read(); for(re int i=1;i<=es;++i) { x=read(),y=read(); if(x>n||y>m) continue;//这是题目的锅 add(x,y); } Maxmatch(); printf("%d\n",ans); return 0; }

最大流\(Dinic\)做法

//网络最大流Dinic做法 #include<cstdio> #include<iostream> #include<algorithm> #include<queue> #include<cstring> #define maxn 20005 #define maxm 2000010 using namespace std; const int INF =999999999; queue<int> q; int kx[maxm],ky[maxm],cnt1; int n,m,x,y,z,es,maxflow,deep[maxn]; int s,t; struct Edge{ int v,nxt=-1,w; }e[maxm<<2]; int cnt=-1,head[maxn]; void add(int x,int y,int z) { e[++cnt].v=y; e[cnt].w=z; e[cnt].nxt=head[x]; head[x]=cnt; } int BFS() { memset(deep,0,sizeof(deep));//deth记录深度 while(!q.empty()) q.pop(); deep[s]=1; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i!=-1;i=e[i].nxt) { if(e[i].w>0&&deep[e[i].v]==0)//分层 { deep[e[i].v]=deep[u]+1; q.push(e[i].v); } } } if(deep[t]!=0)//当汇点的深度不存在时,说明不存在分层图,同时也说明不存在增广路 return 1; else return 0; } int dfs(int now,int limit) { if(now==t) return limit; for(int i=head[now];i!=-1;i=e[i].nxt) { //cur[now]=i; if((deep[e[i].v]==deep[now]+1)&&(e[i].w!=0)) { int di=dfs(e[i].v,min(limit,e[i].w)); if(di>0) { e[i].w-=di; e[i^1].w+=di; return di; } } } return 0; } int Dinic() { int ans=0; while(BFS()) while(int tmp=dfs(s,INF)) ans+=tmp; return ans; } int main() { // freopen("1.in","r",stdin); memset(head,-1,sizeof(head)); scanf("%d%d%d",&n,&m,&es); s=0,t=n+m+1; for(register int i=1;i<=n;++i) { add(s,i,1); add(i,s,0); } for(register int i=1;i<=m;++i) { add(i+n,t,1); add(t,i+n,0); } for(register int i=1;i<=es;++i) { scanf("%d%d",&x,&y); if(x>n||y>m) continue; kx[++cnt1]=x; ky[cnt1]=y; } for(register int i=1;i<=cnt1;++i) { add(kx[i],ky[i]+n,1); add(ky[i]+n,kx[i],0); } cout<<Dinic(); //printf("%d",maxflow); return 0; }

时间复杂度

匈牙利算法的时间复杂度是\(O(n \times m)\),但事实上,对于绝大部分的二分图,匈牙利算法都跑不够上限。\(Dinic\)的时间复杂度是\(O(n \times\sqrt{m})\)关于网络流的讲解,之后会更新。

转载于:https://www.cnblogs.com/Liuz8848/p/10919737.html

最新回复(0)