二分图匹配模版及题型总结
来源:互联网 发布:黑马人工计划软件 编辑:程序博客网 时间:2024/05/14 12:43
今天主要学习了二分图的概念和各种二分图的题型及解决办法。二分图,即所有点可以被分为两个集合,这两个集合中两个点互不相邻。即可以分成左边和右边两堆不相邻的点的图。。首先先是学习了二分图的概念以及如何判断一个图是否是二分图。方法很简单,就是判断有没有奇环,用染色法DFS即可。
模版如下
int col[N];int judge(int u ,int co){//DFS染色判断是否是二分图col[u] = co;for(int i=head[u];i!=-1;i = e[i].next){int v = e[i].v;if(!col[v]){col[v] = -col[u];if(!judge(v,-co))return 0;}else if(col[v] == col[u])return 0;}return 1;}int isBigraph(){//有可能存在不连通的多个块memset(col,0,sizeof(col));for(int u = 1;u<=v;u++)if(col[u]==0)if(!judge(u,-1))return 0;return 1;}
//邻接表int dfs(int u){for(int i= head[u];i!=-1;i = e[i].next){int v = e[i].v;if(!check[v]){check[v] = 1;if(match[v] == -1 || dfs(match[v])){match[v] = u;return 1;}}}return 0;}int hungarian(){int ans = 0;memset(match,-1,sizeof(match));for(int u = 1;u<=n;u++){//根据题目改一下memset(check,0,sizeof(check));if(dfs(u))ans++;}return ans;}//邻接矩阵bool dfs(int u) { for (int i = 1; i <= cnt; ++i) { if (g[u][i] && !visit[i]) //如果节点i与u相邻并且未被查找过 { visit[i] = true; //标记i为已查找过 if (match[i] == -1 || dfs(match[i])) //如果i未在前一个匹配M中,或者i在匹配M中,但是从与i相邻的节点出发可以有增广路径 { match[i] = u; //记录查找成功记录,更新匹配M(即“取反”) return true; //返回查找成功 } } } return false; } int MaxMatch() { int i,sum=0; memset(match,-1,sizeof(match)); for(i = 1 ; i <= cnt ; ++i) { memset(visit,false,sizeof(visit)); //清空上次搜索时的标记 if( dfs(i) ) //从节点i尝试扩展 { sum++; } } return sum; }
第三种是最优匹配,由于还没看 先留空。。。。
题型总结:
一、判断一个图是否是二分图
Uva 10004 Bicoloring
题意:给一张图,图上有一些点和边,问你是否能把这张图上的所有点染色,使得每条边的两个点颜色不同
分析:就是很裸的一道题,问这个图是不是二分图,其实直接模拟染色就可以了,也就是判断的标准做法。
代码:
#include <iostream>#include <cstdio>#include <cstring>using namespace std;struct edge{ int u,v,next;}e[405];int head[205],n,cnt,m;void addedge(int u,int v){ e[cnt].u = u; e[cnt].v = v; e[cnt].next = head[u]; head[u] = cnt++;}int col[205];int judge(int u ,int co){//DFS染色判断是否是二分图col[u] = co;for(int i=head[u];i!=-1;i = e[i].next){int v = e[i].v;if(!col[v]){col[v] = -col[u];if(!judge(v,-co))return 0;}else if(col[v] == col[u])return 0;}return 1;}int isBigraph(){//有可能存在不连通的多个块memset(col,0,sizeof(col));for(int u = 0;u<n;u++)if(col[u]==0)if(!judge(u,-1))return 0;return 1;}int main(){ while(scanf("%d",&n),n){ scanf("%d",&m); int u,v; cnt = 0; memset(head,-1,sizeof(head)); for(int i=1;i<=m;i++) scanf("%d%d",&u,&v),addedge(u,v),addedge(v,u); if(isBigraph())puts("BICOLORABLE."); else puts("NOT BICOLORABLE."); } return 0;}
POJ 1274 The Perfect Stall
题意:有一些牛和一些牛栏,每头牛有自己心仪的牛栏,每个牛栏只能装一头牛,求最多能给多少头牛分配自己心仪的牛栏
分析:很裸很裸的求匹配。。把牛放在一边,牛栏放在另一边建图求匹配即可
代码:
#include <iostream>#include <cstdio>#include <cstring>using namespace std;int g[205][205],check[205],match[205];int n,m;int dfs(int u){for(int i= 1;i<=m;i++){if(!check[i]&&g[u][i]){check[i] = 1;if(match[i] == -1 || dfs(match[i])){match[i] = u;return 1;}}}return 0;}int hungarian(){int ans = 0;memset(match,-1,sizeof(match));for(int u = 1;u<=n;u++){ memset(check,0,sizeof(check)); if(dfs(u)) ans++;}return ans;}int main(){ while(scanf("%d%d",&n,&m)==2){ memset(g,0,sizeof(g)); int flag = 1; for(int i=1;i<=n;i++){ int s,v; scanf("%d",&s); if(!s)flag = 0; while(s--){ scanf("%d",&v); g[i][v] =1; } } if(!n||!m||!flag){puts("0");continue;} printf("%d\n",hungarian()); } return 0;}
POJ 2584 T-Shirt Gumbo
题意:有一些比赛者,需要举办方给发放T恤,然而每个比赛者有一些自己可以接受的尺寸范围,总共有五个尺寸,每个尺寸有固定件的衣服,问是否能够满足每个比赛者都能分到合适的衣服
分析:把每件衣服当作一个点来做,然后在每个人和他可以穿的衣服之间连边建图,求最大匹配,如果和人的数量相同,就可以分到。
代码:
#include <iostream>#include <cstdio>#include <cstring>#include <map>using namespace std;char temp[20],contest[25][5],cloth[105];int g[30][150];int match[150],check[150];int x,n;char si[10] = "SMLXT";int dfs(int u){for(int i = 1;i<=n;i++){if(!check[i]&&g[u][i]){check[i] = 1;if(match[i] == -1 || dfs(match[i])){match[i] = u;return 1;}}}return 0;}map<char ,int > ma;int hungarian(){int ans = 0;memset(match,-1,sizeof(match));for(int u = 1;u<=x;u++){memset(check,0,sizeof(check));if(dfs(u))ans++;}return ans;}int main(){ for(int i=0;i<5;i++)ma[si[i]] = i+1; while(scanf("%s",temp),strcmp(temp,"ENDOFINPUT")!=0){ scanf("%d",&x); memset(g,0,sizeof(g)); for(int i=1;i<=x;i++)scanf("%s",contest[i]); int ts[6]; ts[0] = 0; for(int i=1;i<=5;i++){ scanf("%d",&ts[i]); ts[i]+=ts[i-1]; } n = ts[5]; scanf("%*s"); for(int i=1;i<=x;i++){ int a = ma[contest[i][0]],b = ma[contest[i][1]]; //printf("st:%d ed:%d\n",a,b); for(int j=ts[a-1]+1;j<=ts[b];j++)g[i][j] = 1;//printf("fff:%d %d\n",i,j); } if(hungarian()==x)printf("T-shirts rock!\n"); else printf("I'd rather not wear a shirt anyway...\n"); } return 0;}
POJ 2536 Gopher II
题意:有一些地鼠,在一些固定的点上,有一只老鹰要来抓他们。然而有一些洞, 也是固定坐标。所有地鼠的速度都是固定的,如果不在S秒内跑回洞,那么就会被老鹰吃掉。求出最少被老鹰吃掉的数量。
分析:只要算出每只地鼠能在固定时间内到达的洞,并连边,求出最大匹配就是能逃回洞的地鼠数量,然后要注意题目要求的是被吃掉的数量,所以要用n-ans.
代码:
#include <iostream>#include <cstdio>#include <cstring>#include <cmath>using namespace std;struct point{ double x,y;};point gopher[105],hole[105];int g[105][105];int n,m,s,v;double d(point a,point b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}int check[105],match[105];int dfs(int u){for(int i=1;i<=m;i++){if(!check[i]&&g[u][i]){check[i] = 1;if(match[i] == -1 || dfs(match[i])){match[i] = u;return 1;}}}return 0;}int hungarian(){int ans = 0;memset(match,-1,sizeof(match));for(int u = 1;u<=n;u++){memset(check,0,sizeof(check));if(dfs(u))ans++;}return ans;}int main(){ while(scanf("%d%d%d%d",&n,&m,&s,&v)==4){ memset(g,0,sizeof(g)); for(int i=1;i<=n;i++) scanf("%lf%lf",&gopher[i].x,&gopher[i].y); for(int i=1;i<=m;i++) scanf("%lf%lf",&hole[i].x,&hole[i].y); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(d(gopher[i],hole[j])<=s*v) g[i][j] = 1; printf("%d\n",n-hungarian()); } return 0;}
POJ 2446 Chessboard
题意:给一个M*N的棋盘,并且棋盘上有一些格子有障碍,问是否能用1*2的矩形来将其剩余格子填满,不能重叠。
分析:对于每个不是障碍的格子,可以把这个格子和其相邻的格子分成两个集合,即这整个图就是一个二分图。我们直接求出这个二分图的最大匹配,就是能放的方块数*2(因为建图时每条边会被计算两次)对于这种图的DFS可以改一下,改成往四个方向DFS即可。求出最大匹配后再与无障碍的格子数比较。在计算的时候如果无障碍格子是奇数可以直接跳出循环(奇偶剪枝)
AC代码:
#include <iostream>#include <cstdio>#include <cstring>using namespace std;int n,m,k,cnt;int dx[4] = {1,-1,0,0};int dy[4] = {0,0,1,-1};int check[40][40],match[40][40][2],id[40][40];int dfs(int x,int y){for(int i=0;i<4;i++){ int nx = x+dx[i],ny = y+dy[i]; if(nx<1||ny<1||nx>n||ny>m)continue; if(id[nx][ny]==-1)continue;if(!check[nx][ny]){check[nx][ny] = 1;if(match[nx][ny][0] == -1&&match[nx][ny][1]==-1 || dfs(match[nx][ny][0],match[nx][ny][1])){match[nx][ny][0] = x;match[nx][ny][1] = y;return 1;}}}return 0;}int hungarian(){int ans = 0;memset(match,-1,sizeof(match));for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(id[i][j]!=-1){memset(check,0,sizeof(check));if(dfs(i,j))ans++;}return ans;}int main(){ while(scanf("%d%d%d",&m,&n,&k)==3){ int x,y; cnt = 0; memset(id,0,sizeof(id)); for(int i=1;i<=k;i++){ scanf("%d%d",&x,&y); id[x][y] = -1; } if((m*n-k)%2){puts("NO");continue;} if(hungarian()==m*n-k) puts("YES"); else puts("NO"); } return 0;}
二、最小点覆盖
根据学习的PPT,二分图有许多定理,其中就有关于最小点覆盖的,什么是最小点覆盖?假设你覆盖了图中的一个点就代表覆盖了与这个点相连的所有边,问最少覆盖几个点能把所有边覆盖,这就是最小点覆盖。由König定理知道,最小点覆盖就等于最大匹配数(证明就略了。。。),于是就可以开始做题了。。
POJ 3041 Asteroids
题意:一个N*N的方格,某些格子有一些小行星,你有一种武器,每射击一次可以消灭一行或者一列上的小行星。问要把所给方格中所有小行星都消灭,最少需要射击几次。
分析:所给的是小行星的坐标,我们不妨把所给格子的每一行作为一个点,每一列作为一个点,若这一行这一列存在小行星则连边,构造一个二分图。这样的话我只需要找到某些行和列,每条边都代表一颗小行星,找到最少的行和列包括所有小行星即可。这就是求最小点覆盖。
AC代码如下:
#include <iostream>#include <cstdio>#include <cstring>using namespace std;int g[505][505],match[505],check[505];int r,c;int n,k;int dfs(int u){for(int v = 1;v<=n;v++){if(!check[v]&&g[u][v]){check[v] = 1;if(match[v] == -1 || dfs(match[v])){match[v] = u;return 1;}}}return 0;}int hungarian(){int ans = 0;memset(match,-1,sizeof(match));for(int u = 1;u<=n;u++){memset(check,0,sizeof(check));if(dfs(u))ans++;}return ans;}int main(){ while(scanf("%d%d",&n,&k)==2){ memset(g,0,sizeof(g)); for(int i=1;i<=k;i++) scanf("%d%d",&r,&c),g[r][c] = 1; printf("%d\n",hungarian()); } return 0;}
题意:有两台机器A,B,A机器有N种工作模式,B机器有M种工作模式,有一些产品,可以在A机器的第x种模式下生产,也可以在b机器的第y种模式下生产,问最少改变几次机器的模式,把所有产品都生产出来。
分析:其实和上一题是异曲同工,每种产品当作一条边,在AB的所有模式中选出最少的一些模式覆盖所有边,就是算最小点覆盖。注意这题模式是从0开始的,所以如果两个模式中有一个模式是0就不需要建边了。
代码如下:
#include <iostream>#include <cstdio>#include <cstring>using namespace std;int g[105][105];int a,b,num;int check[105],match[105];int dfs(int u){ for(int v=0;v<b;v++){ if(!check[v]&&g[u][v]){ check[v] = 1; if(match[v] == -1|| dfs(match[v])){ match[v] = u; return 1; } } } return 0;}int hungarian(){ int ans = 0; memset(match,-1,sizeof(match)); for(int u=0;u<a;u++){ memset(check,0,sizeof(check)); if(dfs(u))ans++; } return ans;}int main(){ while(scanf("%d",&a),a){ scanf("%d%d",&b,&num); memset(g,0,sizeof(g)); for(int i=0;i<num;i++){ int u,v; scanf("%*d%d%d",&u,&v); if(!u||!v)continue; g[u][v] = 1; } printf("%d\n",hungarian()); } return 0;}
题意:给一块地,有些格子上是泥。用宽为1的木板去铺这些泥地,不能把草地也给盖上,但是木板可以重叠。木板长度不限。问最少用几块木板可以铺上。
分析:其实和小行星那题很像,唯一有区别的是只能铺连续的一块地。所以直接把连续的一块地作为一个点就可以了。其他做法和小行星那题如出一辙。
AC代码:
#include <iostream>#include <cstring>#include <cstdio>using namespace std;int ma[55][55][2];int g[700][700];int r,c,n,m;char gra[55][55];int vis[700],match[700];int dfs(int u){ for(int i=1;i<=m;i++){ if(!vis[i]&&g[u][i]){ vis[i] = 1; if(!~match[i]|| dfs(match[i])){ match[i] = u; return 1; } } } return 0;}int hungarian(){ int ans = 0; memset(match,-1,sizeof(match)); for(int u=1;u<=n;u++){ memset(vis,0,sizeof(vis)); if(dfs(u))ans++; } return ans;}int main(){ while(scanf("%d%d",&r,&c)==2){ for(int i=0;i<r;i++) scanf("%s",gra[i]); n = m = 0; memset(g,0,sizeof(g)); memset(ma,0,sizeof(ma)); for(int i=1;i<=r;i++) for(int j=1;j<=c;j++){ while(gra[i-1][j-1]=='.'&&j<=c)j++; if(j>c)break; while(gra[i-1][j-1]=='*'&&j<=c){ ma[i][j][0] = n+1; j++; } n++; } for(int j=1;j<=c;j++) for(int i=1;i<=r;i++){ while(gra[i-1][j-1]=='.'&&i<=r)i++; if(i>r)break; while(gra[i-1][j-1]=='*'&&i<=r){ ma[i][j][1] = m+1; i++; } m++; } for(int i=1;i<=r;i++) for(int j=1;j<=c;j++) if(gra[i-1][j-1]=='*') g[ ma[i][j][0] ][ ma[i][j][1] ] = 1; printf("%d\n",hungarian()); } return 0;}
三、最大独立集
什么是最大独立集?就是取出点集中的一些点,使得这些点互相独立,没有边相连。这样的点最多的集合就是最大独立集。由二分图的性质,最大独立集 = 点数 - 最大匹配数,证明略。
POJ 2724 Purifying Machine
题意:一个人有一些奶酪,他有一台净化奶酪的机器,上面有N个开关,每个开关有三种状态,1,0和*,如果是*代表可以是0也可以是1,每次最多只有一个开关是*,每次会把所有开关序列所代表的二进制数的奶酪给净化了。有一天他的机器被污染了,他发现的时候已经污染了一些奶酪,他立即清洗了机器,问最少几次能把被污染的奶酪重新净化。
分析:其实就是给了一些二进制数,问最少几次能把这些数遍历了,因为每次可以遍历两个二进制位之差一位的数,所以可以把给的数中可以一次遍历的两个数连一条边,构成了一个二分图。可以知道,这个二分图的最大独立集就是所求的遍历次数。
判断两个数是否差一位的方法:c = a^b;if(c && (c&(c-1)) == 0) return 1;
AC代码:
#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int N = 1100;int n,m;int num[N],g[N][N];int vis[N];int cnt ;int match[N],check[N];int dfs(int u){ for(int i=1;i<=cnt;i++){ if(!check[i]&&g[u][i]){ check[i] = 1; if(match[i] == -1 || dfs(match[i])){ match[i] = u; return 1; } } } return 0;}int hungarian(){ int ans = 0; memset(match,-1,sizeof(match)); for(int u=1;u<=cnt;u++){ memset(check,0,sizeof(check)); if(dfs(u))ans++; } return ans;}int main(){ while(scanf("%d%d",&n,&m),n+m){ memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); memset(g,0,sizeof(g)); char str[20]; cnt = 0; for(int i=1;i<=m;i++){ scanf("%s",str); int num1 = 0,num2 = 0; for(int j=0;j<n;j++){ if(str[j] == '1')num1+=(1<<(n-j-1)),num2+=(1<<(n-j-1)); else if(str[j] == '*')num1+=(1<<(n-j-1)); } if(num1 == num2&&!vis[num1])vis[num1] = 1,num[++cnt] = num1; else{ if(!vis[num1])vis[num1] = 1,num[++cnt] = num1; if(!vis[num2])vis[num2] = 1,num[++cnt] = num2; } } for(int i=1;i<=cnt;i++){ for(int j=1;j<=cnt;j++){ int c = num[i]^num[j]; if(c && (c&(c-1))==0)g[i][j] = 1; } } printf("%d\n",cnt-hungarian()/2); } return 0;}
在有向无环图中找一些不相交的路径,使得这些路径覆盖图的所有顶点,这样的覆盖中路径数最少的成为最小路径覆盖,而二分图的最小路径覆盖数 = 顶点数 - 最大匹配数,证明略
POJ 2060 Taxi Cab Scheme
题意:有一些出租车的预约单,分别包含出发时间,出发地点坐标和目的地地点坐标。出租车在两地之间需要的时间是这两个地点的曼哈顿距离。要求出租车在出发时间前一分钟到达出发地点。问最少需要几辆出租车来完成这些预约。
分析:把预约作为点,如果两个预约单之间可以由一辆出租车完成,所以每一条路径都可以由一辆出租车完成,所以就变成了求最小路径覆盖。建完图直接求出来即可。
AC代码:
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>//纯粹是为了absusing namespace std;struct node{ int t,sx,sy,ex,ey;}cab[510];int n,g[510][510];int vis[510],match[510];int dfs(int u){ for(int i=1;i<=n;i++){ if(!vis[i]&&g[u][i]){ vis[i] = 1; if(match[i] == -1|| dfs(match[i])){ match[i] = u; return 1; } } } return 0;}int hungarian(){ int ans = 0; memset(match,-1,sizeof(match)); for(int u=1;u<=n;u++){ memset(vis,0,sizeof(vis)); if(dfs(u))ans++; } return ans;}int main(){ int T; cin>>T; while(T--){ scanf("%d",&n); memset(g,0,sizeof(g)); for(int i=1;i<=n;i++){ int h,m; node &temp = cab[i]; scanf("%d:%d%d%d%d%d",&h,&m,&temp.sx,&temp.sy,&temp.ex,&temp.ey); temp.t = h*60+m; } for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ node &a = cab[i]; node &b = cab[j]; int t1 = abs(a.ex-a.sx)+abs(a.ey-a.sy); int t2 = abs(b.sx-a.ex)+abs(b.sy-a.ey); if(a.t+t1+t2+1<=b.t)g[i][j] = 1; } } printf("%d\n",n-hungarian()); } return 0;}
暂时就贴这么多题,做完这些题,发现二分图的应用其实特别广泛,没有奇环这个性质还是十分容易满足的,二分图的性质又非常特殊,以后如果对于图论的一些覆盖、匹配问题,一定可以看看是否是一个二分图再做。
- 二分图匹配模版及题型总结
- 二分匹配 模版 及最大总结 及 题目集合 +解析
- 无权边二分图的 二分匹配 模版及解析
- 【待重置】二分图最大匹配各种题型
- 二分图匹配之匈牙利算法和 二分图匹配的几种题型
- 二分图的最优匹配模版
- POJ 2239 二分图最大匹配模版
- hdu1150(二分图匹配模版题)
- 二分匹配模版
- 二分匹配模版
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- 二分图匹配总结
- Leetcode【34】:Search insert Position
- 让elasticsearch(es)分片分布在不同的机器上
- POJ3295
- XDOJ1178 - 角划分平面
- Elasticsearch store属性理解
- 二分图匹配模版及题型总结
- poj1149 最大流 PIGS
- f发个链接看看
- 佛祖保佑 永无bug
- Java虚拟机的启动与程序的运行
- iOS_导入libxml2.2框架后,找不到<libxml/tree.h>的解决办法
- 资源收藏:扁平化风格的图标
- BZO3065 带插入区间K小值
- FILE上传图片