二分图匹配问题 最大匹配以及相关结论/多重匹配/最大带权匹配/带花树算法
二分图匹配问题:
做法:
①匈牙利算法,时间复杂度O(N*V)
②Hopcroft-Karp,时间复杂度O(√N*V)
相关结论:
①最小顶点覆盖(könig定理)
二分图的最小顶点覆盖=最大匹配数
②最小路径覆盖(不要求二分图):在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关
最小路径覆盖 = 顶点数 - 最大匹配配
对于有向无环图,首先拆点,建成二分图再进行求解
·最小不相交路径覆盖
建图方式:把一个的点V拆点成Vx和Vy,如果A连向B,那么就建一条Ax连向By的边。
图中有多少条路径,可以以一种方法得到,就是计算出度为0的点的个数。如果知道这个就很容易得出这个结论了
·最小相交路径覆盖
做法首先跑floyd,求出原图的传递闭包,然后用上述方法做即可
③最小边覆盖
最小边覆盖=图顶点-最大匹配
首先一开始,假如一条边都不选的话,要覆盖所有的点就必须每个点都选一次,也就是n次,然后每选一条边就会减少1个,所以结论显而易见
④最大独立集
最大独立集=图顶点-最大匹配=最小边覆盖
二分图的独立数等于顶点数减去最大匹配数,很显然的把最大匹配两端的点都从顶点集中去掉这个时候剩余的点是独立集,这是|V|-2*|M|,同时必然可以从每条匹配边的两端取一个点加入独立集并且保持其独立集性质。
二分图多重匹配
( 一 ) 如果x部节点只对应一个y部节点,而y部节点可以对应多个x部节点,那么这种匹配可以用匈牙利算法来解决
解决的问题:一个y最多匹配cnt个x是否成立,要问一个y匹配人数最大的最小值可以用二分答案来做
解决思路:根据匈牙利算法的思想,这时的link[u]要变成link[u][i],表示与y[u]匹配好了的第i个点,用vlink[u]记录已经于u点匹配了的点的个数,对于x中的x[k],找到一个与他相连的y[i]后,同样判断匈牙利算法中的两个条件是否成立,若满足第一个条件,直接将x[k],y[i]匹配,否则,如果与y[i]所匹配的点已经达到了饱和,那么在所有与y[i]配合的点中选一个点,检查能否找到增广路,如果能,就让出位置让x[k]与y[i]匹配
( 二 )如果x部节点可以匹配多个y部节点,y部节点可以同时匹配多个x部节点,那么应该用网络流来解决。(因为匈牙利算法无法应对两边都可以选多个这种情况)
如何建图:假设x部节点的容量为capx[ i ],y部节点的容量为capy[ i ],同时给出x部节点可以与y部节点相连的的边,那么对于每个x部节点,超级源点都与x部节点连边,边权为capx[i];对于每个y部节点,都与超级汇点连接边,边权为capy[i]。然后连接每个x与y直接相连的边,边权为1。
这样一来,求出最大流就是最大匹配方案了:流量通道上的边的剩余流量代表匹配结果
带权二分图的权值最大的完备匹配
做法:KM算法,时间复杂度O(N3)
相关博客:
https://blog.csdn.net/qq_37943488/article/details/78586048
https://www.cnblogs.com/zpfbuaa/p/7218607.html
一般图匹配问题
做法:带花树算法(会套板子即可)
描述:一般图与二分图的主要差别都集中在了奇环上,所以带花树最大的目标,就是解决奇环对算法的影响。
首先,我们仍然当做是二分图来做:仍然是暴力找增广路径,对于我们枚举到的相邻点v
若v未访问过:1、若v已经匹配,则从v开始继续bfs2、若v未匹配,则找到一条增广路
若v访问过,则找到一个环:1、若为偶环,直接忽略,跳过当前节点2、若为奇环,则将当前的环暴力缩点,将环缩成一朵陈村花
HDU1045 Fire Net
题意:
在一个n*n的地图上放置炮台,每个炮台之间不能相互攻击(在同一行或一列上),如果中间放有障碍物则可以抵挡相互攻击,现在问最多能放多少个炮台
思路:
将每行中每列中被障碍物分割的区域分别进行缩点成为不同的点
将横坐标分成一组,纵坐标分成一组进行二分图最大匹配
#include<iostream> #include<algorithm> #include<vector> #include<cstring> using namespace std; const int maxn=100; char s[maxn][maxn]; int idx[maxn][maxn],idy[maxn][maxn],matching[maxn],check[maxn]; int n,cntx,cnty; vector<int> a[maxn]; bool dfs(int u) { for (int i=0;i<a[u].size();i++){ int v = a[u][i]; if (!check[v]) { check[v] = true; if (matching[v]==-1||dfs(matching[v])) { matching[v]=u; matching[u]=v; return true; } } } return false; } int hungarian(int cntx) { int ans=0; memset(matching,-1,sizeof(matching)); for (int u=0;u<=cntx;++u){ if(matching[u]==-1){ memset(check, 0, sizeof(check)); if(dfs(u)) ++ans; } } return ans; } void init() { memset(idx,0,sizeof(idx)); memset(idy,0,sizeof(idy)); memset(s,0,sizeof(s)); memset(matching,0,sizeof(matching)); for(int i=0;i<maxn;i++) a[i].clear(); cntx=cnty=0; } int main() { while(scanf("%d",&n)&&n){ init(); for(int i=0;i<n;i++) scanf("%s",s[i]); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(s[i][j]=='.') idx[i][j]=cntx; else if(s[i][j]=='X'&&s[i][j+1]!='X') cntx++; } if(s[i][n-1]!='X'&&s[i+1][0]!='X'&&i!=n-1) cntx++; for(int j=0;j<n;j++){ if(s[j][i]=='.') idy[j][i]=cnty; else if(s[j][i]=='X'&&s[j+1][i]!='X') cnty++; } if(s[n-1][i]!='X'&&s[0][i+1]!='X'&&i!=n-1) cnty++; } for(int i=0;i<n;i++) for(int j=0;j<n;j++) if(s[i][j]!='X') a[idx[i][j]].push_back(idy[i][j]+cntx+1); cout<<hungarian(cntx)<<endl; } return 0; }
HDU 2444 The Accomodation of Students
题意:
有一些同学是相互认识的,现在要求将同学分成两批,每批中同学相互认识,求两批人中的最大匹配数
思路:
先用染色法判断是否为二分图,并且将点分组
如果为二分图,就进行最大匹配
#include<iostream> #include<algorithm> #include<cstring> #include<vector> #include<queue> using namespace std; const int maxn=205; vector<int> a[maxn],l; int color[maxn],matching[maxn],check[maxn],vis[maxn]; int n,m,u,v; void init() { memset(matching,-1,sizeof(matching)); memset(color,-1,sizeof(color)); memset(vis,0,sizeof(vis)); for(int i=0;i<maxn;i++) a[i].clear(); l.clear(); } bool judge() { color[1]=0; queue<int> q; q.push(1); vis[1]=1; while(!q.empty()){ int u=q.front(); if(color[u]==0) l.push_back(u); q.pop(); for(int i=0;i<a[u].size();i++){ int v=a[u][i]; if(vis[v]){ if(color[u]==color[v]) return false; else continue; } else{ vis[v]=1; color[v]=color[u]^1; q.push(v); } } } return true; } bool dfs(int u) { for (int i=0;i<a[u].size();++i){ int v=a[u][i]; if (!check[v]){ check[v]=true; if (matching[v]==-1||dfs(matching[v])){ matching[v]=u; matching[u]=v; return true; } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int u=1;u<=n;++u) { if(matching[u]==-1) { memset(check,0,sizeof(check)); if (dfs(u)) ++ans; } } return ans; } int main() { while(scanf("%d%d",&n,&m)!=EOF){ init(); for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); a[v].push_back(u); } if(!judge()) cout<<"No"<<endl; else cout<<hungarian()<<endl; } return 0; }
HDU 1083 Courses
题意:
有一些同学对一些课程感兴趣,现在要求每门课最多只能有一个同学上,问是否所有课都有人上
思路:
裸题,直接进行匹配就好了
#include<iostream> #include<algorithm> #include<vector> #include<cstring> using namespace std; const int maxn=405; vector<int> a[maxn]; int matching[maxn],check[maxn]; int n,m,t,cnt,u,v; bool dfs(int u) { for(int i=0;i<a[u].size();i++){ int v=a[u][i]; if(!check[v]){ check[v]=true; if(matching[v]==-1||dfs(matching[v])){ matching[u]=v; matching[v]=u; return true; } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=n;i++){ if(matching[i]==-1){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } } return ans; } int main() { scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=0;i<=n+m;i++) a[i].clear(); for(int i=1;i<=n;i++){ scanf("%d",&cnt); while(cnt--){ scanf("%d",&u); a[i].push_back(u+n); a[u+n].push_back(i); } } if(hungarian()==n) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }
HDU 1281 棋盘游戏
题意:
在一个n*n的棋盘上,有些点可以放棋子,要求棋子不相互攻击问最多能放多少个棋子,如果将一些位置移除,最大匹配会减少则称为关键节点,再求出关键节点有多少
思路:
先进行二分图匹配求出最大匹配数
然后将每个可行点从棋盘中移去看看最大匹配数是否会减少
(邻接矩阵跟邻接表的板子略有不同。。被卡了好久)
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int maxn=105; int matching[maxn],check[maxn],s[2*maxn][2*maxn]; int n,m,Cas=1,k,u,v,ans1,ans2; void init() { memset(s,0,sizeof(s)); ans1=ans2=0; } bool dfs(int u) { for(int i=1;i<=m;i++){ if(s[u][i]){ if(!check[i]){ check[i]=true; if(matching[i]==-1||dfs(matching[i])){ //matching[u]=i; matching[i]=u; return true; } } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=n;i++){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } return ans; } int main() { while(scanf("%d%d%d",&n,&m,&k)!=EOF){ init(); for(int i=1;i<=k;i++){ scanf("%d%d",&u,&v); s[u][v]=1; } ans2=hungarian(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(s[i][j]){ s[i][j]=0; if(hungarian()<ans2) ans1++; s[i][j]=1; } } } printf("Board %d have %d important blanks for %d chessmen.\n",Cas++,ans1,ans2); } return 0; }
HDU 2819 Swap
题意:
给一个01矩阵,你可以交换任意行或列,问你能否使得矩阵的主对角线上全为1
思路:
如果主对角线上全为1的话,那么矩阵的秩就为n,并且进行矩阵的初等变换矩阵的秩不会改变,所以判断能否变换成功只需判断矩阵的秩是否为1
如何进行判断呢,这就需要二分图进行建模,首先进行二分图拆点,拆位横坐标与纵坐标,如果Aij=1的话就连一条边,然后进行二分图匹配
如果最大匹配为n则可以变换,最后只需要判断matching[i]==i,如果不等于i的话找到matching[j]=i进行交换即可
#include<iostream> #include<algorithm> #include<cstring> #include<vector> using namespace std; const int maxn=105; int matching[maxn],check[maxn],s[maxn][maxn],n; struct node{ int x,y; node(int x1,int y1):x(x1),y(y1){} }; vector<node> a; bool dfs(int u) { for(int i=1;i<=n;i++){ if(s[u][i]){ if(!check[i]){ check[i]=true; if(matching[i]==-1||dfs(matching[i])){ matching[i]=u; return true; } } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=n;i++){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } return ans; } int main() { while(scanf("%d",&n)!=EOF){ a.clear(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&s[i][j]); if(hungarian()<n) cout<<-1<<endl; else{ for(int i=1;i<=n;i++){ if(i!=matching[i]){ for(int j=1;j<=n;j++){ if(matching[j]==i){ swap(matching[i],matching[j]); a.push_back(node(i,j)); } } } } cout<<a.size()<<endl; for(int i=0;i<a.size();i++) cout<<"C "<<a[i].x<<" "<<a[i].y<<endl; } } return 0; }
HDU 2389 Rain on your Parade
题意:
初始时每个人都在其初始坐标,并且有移动的速度,在t 秒后会下雨,并且在地图的一些位置上有雨伞,一把伞只能一个人撑,问最多有多少人能撑到伞
思路:
先预处理出每个人能拿到哪些伞,并且连边,就会成为一个二分图最大匹配问题
但是匈牙利算法的复杂度为O(NV),就要用到hopcroft-karp算法,时间复杂度为O(√N*V)
#include <bits/stdc++.h> #define maxn 3010 #define inf 0x3f3f3f3f using namespace std; struct Node{ int to,next; }Edge[maxn*maxn]; struct node{ double x,y,v; }a[maxn]; int head[maxn],num; int lx[maxn],ly[maxn],dx[maxn],dy[maxn]; bool vis[maxn]; int T,n,m,t,dis; void init(){ memset(lx,-1,sizeof(lx)); memset(ly,-1,sizeof(ly)); memset(head,-1,sizeof(head)); num = 0; } void add(int u,int v){ Edge[num].to = v; Edge[num].next = head[u]; head[u] = num ++; } double dist(double x, double y, double xx, double yy){ return sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy)); } bool Search(){ queue<int> q; dis = inf; memset(dx,-1,sizeof(dx)); memset(dy,-1,sizeof(dy)); for(int i=0;i<n;i++){ if(lx[i] == -1){ q.push(i); dx[i] = 0; } } while(!q.empty()){ int u = q.front(); q.pop(); if(dx[u] > dis) break; for(int i=head[u];i!=-1;i=Edge[i].next){ int v = Edge[i].to; if(dy[v] == -1){ dy[v] = dx[u] + 1; if(ly[v] == -1) dis = dy[v]; else{ dx[ly[v]] = dy[v] + 1; q.push(ly[v]); } } } } return dis != inf; } bool dfs(int u){ for(int i=head[u];i!=-1;i=Edge[i].next){ int v = Edge[i].to; if(!vis[v] && dy[v] == dx[u] + 1){ vis[v] = true; if(ly[v] != -1 && dy[v] == dis) continue; if(ly[v] == -1 || dfs(ly[v])){ ly[v] = u; lx[u] = v; return true; } } } return false; } int Hopcroft_Karp(){ int sum = 0; while( Search() ){ memset(vis,false,sizeof(vis)); for(int i=0;i<n;i++){ if(lx[i] == -1 && dfs(i)) sum ++; } } return sum; } int main() { int Case = 1; scanf("%d",&T); while(T--){ init(); scanf("%d%d",&t,&n); for(int i=0;i<n;i++){ scanf("%lf%lf%lf",&a[i].x,&a[i].y,&a[i].v); } scanf("%d",&m); for(int i=0;i<m;i++){ double xx, yy; scanf("%lf%lf",&xx,&yy); for(int j=0;j<n;j++){ double zz = dist(xx, yy, a[j].x, a[j].y); if(a[j].v * t >= zz){ add(j ,i); } } } int ans = Hopcroft_Karp(); printf("Scenario #%d:\n%d\n\n",Case ++, ans); } return 0; }
HDU 4185 Oil Skimming
题意:
有一个地图,包含‘.’与‘#’,两个#相连可以得一分,但是不能重复使用,问最多可以得几分
思路:
将地图上的每个‘#’进行拆成两个点构成一张二分图,并且每个‘#’与其相邻‘#’连上一条边
最后进行最大匹配,答案就为最大匹配数/2(因为每个点被匹配了两次)
#include<iostream> #include<algorithm> #include<cstring> #include<map> #include<vector> using namespace std; const int maxn=605; int matching[maxn],check[maxn],s[maxn][maxn]; char ma[maxn][maxn]; map<int,int> m; vector<int> a[maxn]; int t,Cas=1,n,cnt; bool dfs(int u) { for(int i=0;i<a[u].size();i++){ int v=a[u][i]; if(!check[v]){ check[v]=true; if(matching[v]==-1||dfs(matching[v])){ matching[v]=u; return true; } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=cnt;i++){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } return ans; } void init() { m.clear(); for(int i=1;i<=n*n;i++) a[i].clear(); cnt=0; } int main() { scanf("%d",&t); while(t--){ scanf("%d",&n); init(); for(int i=0;i<n;i++){ scanf("%s",ma[i]); for(int j=0;j<n;j++){ if(ma[i][j]=='#') m[i*n+j]=++cnt; } } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(ma[i][j]=='#'){ if(ma[i][j+1]=='#'&&j+1<n) a[m[i*n+j]].push_back(m[i*n+j+1]); if(ma[i+1][j]=='#'&&i+1<n) a[m[i*n+j]].push_back(m[(i+1)*n+j]); if(ma[i-1][j]=='#'&&i-1>=0) a[m[i*n+j]].push_back(m[n*(i-1)+j]); if(ma[i][j-1]=='#'&&j-1>=0) a[m[i*n+j]].push_back(m[i*n+j-1]); } } } cout<<"Case "<<Cas++<<": "<<hungarian()/2<<endl; } return 0; }
POJ 3020 Antenna Placement
题意:
有一个地图,包含‘o’与‘*’,消除一个*可以同时消除一个与之相连的*,问要消除完所有*要几次
思路:
拆点后进行二分图最大匹配,答案就为总*数-最大匹配数
#include<iostream> #include<algorithm> #include<cstring> #include<map> #include<vector> using namespace std; const int maxn=605; int matching[maxn],check[maxn],s[maxn][maxn]; char ma[maxn][maxn]; map<int,int> m; vector<int> a[maxn]; int t,Cas=1,n,cnt,m1; bool dfs(int u) { for(int i=0;i<a[u].size();i++){ int v=a[u][i]; if(!check[v]){ check[v]=true; if(matching[v]==-1||dfs(matching[v])){ matching[v]=u; return true; } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=cnt;i++){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } return ans; } void init() { m.clear(); for(int i=1;i<=n*m1;i++) a[i].clear(); cnt=0; } int main() { scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m1); init(); for(int i=0;i<n;i++){ scanf("%s",ma[i]); for(int j=0;j<m1;j++){ if(ma[i][j]=='*') m[i*m1+j]=++cnt; } } for(int i=0;i<n;i++){ for(int j=0;j<m1;j++){ if(ma[i][j]=='*'){ if(ma[i][j+1]=='*'&&j+1<m1) a[m[i*m1+j]].push_back(m[i*m1+j+1]); if(ma[i+1][j]=='*'&&i+1<n) a[m[i*m1+j]].push_back(m[(i+1)*m1+j]); if(ma[i-1][j]=='*'&&i-1>=0) a[m[i*m1+j]].push_back(m[m1*(i-1)+j]); if(ma[i][j-1]=='*'&&j-1>=0) a[m[i*m1+j]].push_back(m[i*m1+j-1]); } } } int ans=hungarian()/2; ans=cnt-2*ans+ans; cout<<ans<<endl; } return 0; }
HDU 1054 Strategic Game(最小点覆盖)
题意:
一棵树,一个点可以保护所有与其相连的点,问要用最少多少个点可以将整棵树保护
思路:
拆点建图,可以转化为求最小点覆盖问题
二分图最小点覆盖=最大匹配数
#include<iostream> #include<vector> #include<cstdio> #include<cstring> #define maxn 1505 using namespace std; vector<int> g[maxn]; int vis[maxn]; int link[maxn]; bool getnum(int u) { for(int i=0;i<g[u].size();i++) { int v=g[u][i]; if(!vis[v]) { vis[v]=1; if(link[v]==-1||getnum(link[v])) { link[v]=u; return true; } } } return false; } int main() { int n; while(scanf("%d",&n)!=EOF) { int u,m,v; for(int i=0;i<n;i++) { g[i].clear(); } for(int i=0;i<n;i++) { scanf("%d:(%d)",&u,&m); for(int j=0;j<m;j++) { scanf("%d",&v); g[u].push_back(v); g[v].push_back(u); } } memset(link,-1,sizeof(link)); int count=0; for(int i=0;i<n;i++) { memset(vis,0,sizeof(vis));//标记一次用了哪几个点 if(getnum(i)) count++; } printf("%d\n",count/2); } return 0; }
HDU 1151 Air Raid(最小路径覆盖)
题意:
给一张有向无环图,一个人可以从一个点出发不停的走,每个人的路径不能有相交,求最少要多少个人可以把这张图走完
思路:
DAG上的最小路径覆盖,先进行拆点
答案就为顶点数-最大匹配数
#include<iostream> #include<algorithm> #include<vector> #include<cstring> using namespace std; const int maxn=150; int matching[maxn],check[maxn]; vector<int> a[maxn]; int t,n,m,u,v; bool dfs(int u) { for(int i=0;i<a[u].size();i++){ int v=a[u][i]; if(!check[v]){ check[v]=true; if(matching[v]==-1||dfs(matching[v])){ matching[v]=u; return true; } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=n;i++){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } return ans; } int main() { scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) a[i].clear(); for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); a[u].push_back(v); } int ans=n-hungarian(); cout<<ans<<endl; } return 0; }
POJ 2594 Treasure Exploration(最小路径覆盖)
题意:
一个DAG,一个人可以从一个点出发不停的走,每个人的路径可以有相交,求最少要多少个人可以把这张图走完
思路:
求最小路径覆盖,但是一个点可以被经过多次,所以需要先进行floyd闭包传递,再进行最大匹配
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int maxn=505; int matching[maxn],check[maxn],ma[maxn][maxn]; int t,n,m,u,v; void floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(ma[i][k]&&ma[k][j]) ma[i][j]=1; } bool dfs(int u) { for(int i=1;i<=n;i++){ if(!check[i]&&ma[u][i]){ check[i]=true; if(matching[i]==-1||dfs(matching[i])){ matching[i]=u; return true; } } } return false; } int hungarian() { int ans=0; memset(matching,-1,sizeof(matching)); for(int i=1;i<=n;i++){ memset(check,0,sizeof(check)); if(dfs(i)) ans++; } return ans; } int main() { while(scanf("%d%d",&n,&m)&&n){ for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ma[i][j]=0; for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); ma[u][v]=1; } floyd(); int ans=n-hungarian(); cout<<ans<<endl; } return 0; }
HDU 3829 Cat VS Dog(最大独立集)
题意:
每个人都有喜欢的动物和喜欢的动物,如果喜欢狗就会讨厌猫反之也一样,如果一个人讨厌的动物被移出并且喜欢的动物还留下他就会开心,问最多会有多少人开心
思路:
先进行二分图的建模,将喜欢猫跟不喜欢猫的人分成两部分
再将两部分有矛盾的人连起来,之后进行二分图最大匹配,答案就为最大独立集个数
比较难想明白的就是有多个人的喜好跟厌恶相同怎么办,这也是解法比较巧妙的地方
举个例子:如果有A个人喜欢Cx 讨厌Dy , 有B个人喜欢Dy 喜欢Cx 那么进行最大匹配后,会有cnt=min(A,B)个关系相连
将他们这些关系移出后,就会剩下max(A-cnt,B-cnt)的人剩下,从而得到开心人数最大
#include<stdio.h> #include<algorithm> #include<string.h> using namespace std; typedef struct { int c,like,dlike; }H; H l[550],r[550]; int map[550][550],vis[550],flag[550],nx,ny; int find(int k); int main() { int n,i,j,k,sum,m,p,x,y;char a,b; while(scanf("%d%d%d",&n,&m,&p)!=EOF) { nx=1;ny=1; memset(map,0,sizeof(map)); memset(flag,0,sizeof(flag)); for(i=1;i<=p;i++) { scanf(" %c%d %c%d",&a,&x,&b,&y); if(a=='D') { l[nx].like=x; l[nx].dlike=y; l[nx++].c=i; } else { r[ny].like=x; r[ny].dlike=y; r[ny++].c=i; } } for(i=1;i<nx;i++) { for(j=1;j<ny;j++) { if(l[i].like==r[j].dlike) map[l[i].c][r[j].c]=1; if(l[i].dlike==r[j].like) map[l[i].c][r[j].c]=1; } } sum=0; for(i=1;i<nx;i++) { memset(vis,0,sizeof(vis)); if(find(l[i].c)) sum++; } printf("%d\n",p-sum); } return 0; } int find(int k) { int i,j,u,v; for(j=1;j<ny;j++) { u=k;v=r[j].c; // printf("%d %d\n",u,v); if(!vis[v] && map[u][v]) { vis[v]=1; //printf("%d %d\n",u,v); if(!flag[v] || find(flag[v])) { flag[v]=u; return 1; } } } return 0; }
POJ 2289 Jamie's Contact Groups(二分图多重匹配)
题意:
将n个人分组,每个人只能被分在唯一的组中,但是有不同的分组选择,现在求最大组容量的最小值
思路:
二分图多重匹配问题,二分答案然后进行匹配,看看是否可行即可
#include<iostream> #include<algorithm> #include<cstring> #define inf 0x3f3f3f3f using namespace std; const int maxn=1e3+10; int mp[maxn][maxn],matching[maxn][maxn],vx[maxn],check[maxn]; int n,m,pos,mid; char s[100]; bool dfs(int u) { for(int v=0;v<m;v++){ if(mp[u][v]&&!check[v]){ check[v]=1; if(vx[v]<mid){ matching[v][vx[v]++]=u; return true; } for(int i=0;i<vx[v];i++){ if(dfs(matching[v][i])){ matching[v][i]=u; return true; } } } } return false; } bool hungarian() { memset(matching,0,sizeof(matching)); memset(vx,0,sizeof(vx)); for(int i=0;i<n;i++){ memset(check,0,sizeof(check)); if(!dfs(i)) return false; } return true; } int main() { while(scanf("%d%d",&n,&m)&&n&&m){ memset(mp,0,sizeof(mp)); getchar(); for(int i=0;i<n;i++){ gets(s); for(int j=0;s[j];){ while(s[j]>'9'||s[j]<'0') j++; int num=0; while(s[j]<='9'&&s[j]>='0') num=num*10+s[j++]-'0'; mp[i][num]=1; } } int l=0,r=inf; while(l<r){ mid=(l+r)/2; if(hungarian()) r=mid; else l=mid+1; } cout<<r<<endl; } return 0; }
POJ 2112 Optimal Milking(闭包传递+二分答案+多重匹配)
题意:
告诉你每只奶牛到取奶机之间的距离,一个取奶机最多个m只奶牛取奶,问所有奶牛中要走的最大距离是多少
思路:
先进行闭包传递求出最短距离,之后进行二分图多重匹配
二分答案,再多重匹配时要加上距离这个限制条件到额判断
#include<iostream> #include<algorithm> #include<cstring> #define inf 0x3f3f3f3f using namespace std; const int maxn=250+10; int mp[maxn][maxn],matching[maxn][maxn],vx[maxn],check[maxn]; int k,c,m,mid,n,l,r,ans; void floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]); l=min(l,mp[i][j]); r=max(r,mp[i][j]); } } bool dfs(int u) { for(int i=1;i<=k;i++){ if(mp[u][i]<=mid&&!check[i]){ check[i]=1; if(vx[i]<m){ matching[i][vx[i]++]=u; return true; } for(int j=0;j<m;j++){ if(dfs(matching[i][j])){ matching[i][j]=u; return true; } } } } return false; } bool hungarian() { memset(vx,0,sizeof(vx)); for(int i=k+1;i<=k+c;i++){ memset(check,0,sizeof(check)); if(!dfs(i)) return false; } return true; } int main() { while(scanf("%d%d%d",&k,&c,&m)!=EOF){ n=k+c; ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ scanf("%d",&mp[i][j]); if(mp[i][j]==0&&i!=j) mp[i][j]=inf; } } l=inf,r=0; floyd(); while(l<=r){ mid=(l+r)/2; if(hungarian()) r=(ans=mid)-1; else l=mid+1; } cout<<ans<<endl; } return 0; }
POJ 3198 Steady Cow Assignment(枚举答案+多重匹配)
题意:
有n只奶牛进行搬家,每只奶牛对于不同的新房都有满意程度,并且每个房间的容量都有限制,求满意程度差值的最小值
思路:
枚举差值,以及因此产生的最大最小值,然后进行二分图多重匹配判断
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int maxn=1000+5; int mp[maxn][maxn],matching[maxn][maxn],vx[maxn],check[maxn],siz[maxn]; int b,mid,n,l,r,mi; bool dfs(int u) { for(int i=1;i<=b;i++){ if(mp[u][i]<=r&&mp[u][i]>=l&&!check[i]){ check[i]=1; if(vx[i]<siz[i]){ matching[i][vx[i]++]=u; return true; } for(int j=0;j<vx[i];j++){ if(dfs(matching[i][j])){ matching[i][j]=u; return true; } } } } return false; } bool hungarian() { memset(vx,0,sizeof(vx)); for(int i=1;i<=n;i++){ memset(check,0,sizeof(check)); if(!dfs(i)) return false; } return true; } int main() { while(scanf("%d%d",&n,&b)!=EOF){ int temp,ans=0; for(int i=1;i<=n;i++){ for(int j=1;j<=b;j++){ scanf("%d",&temp); mp[i][temp]=j; } } for(int i=1;i<=b;i++) scanf("%d",&siz[i]); l=r=1,ans=b; while(l<=r&&r<=b){ if(hungarian()){ ans=min(ans,r-l+1); l++; } else r++; } cout<<ans<<endl; } return 0; }
HDU 2255 奔小康赚大钱
题意:
有n个人买n个房,每个人对不同的房出价都不同,求房屋出售者的最大收益
思路:
KM模板题,二分图最大带权匹配问题
#include <iostream> #include <cstring> #include <cstdio> #include<algorithm> using namespace std; using namespace std; const int MAXN = 305; const int INF = 0x3f3f3f3f; int love[MAXN][MAXN]; // 记录每个妹子和每个男生的好感度 int ex_girl[MAXN]; // 每个妹子的期望值 int ex_boy[MAXN]; // 每个男生的期望值 bool vis_girl[MAXN]; // 记录每一轮匹配匹配过的女生 bool vis_boy[MAXN]; // 记录每一轮匹配匹配过的男生 int match[MAXN]; // 记录每个男生匹配到的妹子 如果没有则为-1 int slack[MAXN]; // 记录每个汉子如果能被妹子倾心最少还需要多少期望值 int N; bool dfs(int girl) { vis_girl[girl] = true; for (int boy = 0; boy < N; ++boy) { if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次 int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy]; if (gap == 0) { // 如果符合要求 vis_boy[boy] = true; if (match[boy] == -1 || dfs( match[boy] )) { // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人 match[boy] = girl; return true; } } else { slack[boy] = min(slack[boy], gap); // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子【捂脸 } } return false; } int KM() { memset(match, -1, sizeof match); // 初始每个男生都没有匹配的女生 memset(ex_boy, 0, sizeof ex_boy); // 初始每个男生的期望值为0 // 每个女生的初始期望值是与她相连的男生最大的好感度 for (int i = 0; i < N; ++i) { ex_girl[i] = love[i][0]; for (int j = 1; j < N; ++j) { ex_girl[i] = max(ex_girl[i], love[i][j]); } } // 尝试为每一个女生解决归宿问题 for (int i = 0; i < N; ++i) { fill(slack, slack + N, INF); // 因为要取最小值 初始化为无穷大 while (1) { // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止 // 记录每轮匹配中男生女生是否被尝试匹配过 memset(vis_girl, false, sizeof vis_girl); memset(vis_boy, false, sizeof vis_boy); if (dfs(i)) break; // 找到归宿 退出 // 如果不能找到 就降低期望值 // 最小可降低的期望值 int d = INF; for (int j = 0; j < N; ++j) if (!vis_boy[j]) d = min(d, slack[j]); for (int j = 0; j < N; ++j) { // 所有访问过的女生降低期望值 if (vis_girl[j]) ex_girl[j] -= d; // 所有访问过的男生增加期望值 if (vis_boy[j]) ex_boy[j] += d; // 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步! else slack[j] -= d; } } } // 匹配完成 求出所有配对的好感度的和 int res = 0; for (int i = 0; i < N; ++i) res += love[ match[i] ][i]; return res; } int main() { while (~scanf("%d", &N)) { for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) scanf("%d", &love[i][j]); printf("%d\n", KM()); } return 0; }
HDU 3488 Tour(KM算法)
题意:
给一张带权有向图,要求用一些环走完整张图,使得路径权值和最小并且每个点只能属于一个环
思路:
一个点只能属于一个环的话,就意味着一个点只能连出两条边
如果将原图进行拆点变成一个二分图的话就意味,二分图会有一个完备匹配,并且要求最小值
所以题意就转化为求完备匹配的最小值,如何求匹配的最小值呢,可以将权值取反求出的最大值再取反就是最小值
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 205, INF = 0x3f3f3f3f; int n, m, t, u, v, w[N][N], la[N], a, lb[N], mat[N], d, upd[N]; bool va[N], vb[N]; bool dfs(int u) { va[u]=true; for(int v=1;v<=n;v++) { if(vb[v]) continue; if (la[u]+lb[v]-w[u][v]==0) { vb[v]=true; if (!mat[v]||dfs(mat[v])) { mat[v]=u; return true; } } else upd[v]=min(upd[v],la[u]+lb[v]-w[u][v]); } return false; } int KM() { memset(mat,0,sizeof(mat)); for (int i=1;i<=n;i++) { la[i]=-INF; lb[i]=0; for(int j=1;j<=n;j++) { la[i]=max(la[i],w[i][j]); } } for(int i=1;i<=n;i++) { while(1){ memset(va,false,sizeof(va)); memset(vb,false,sizeof(vb)); for(int j=1;j<=n;j++) upd[j]=INF; if(dfs(i)) break; //降低期望 d=INF; for(int j=1;j<=n;j++) if(!vb[j]) d=min(d,upd[j]); for (int j=1;j<=n;j++) { if(va[j]) la[j]-=d; if(vb[j]) lb[j]+=d; } } } int ans=0; for (int i=1;i<=n;i++) ans+=w[mat[i]][i]; return -ans; } int main() { scanf("%d", &t); while (t--) { scanf("%d%d", &n, &m); memset(w, -0x3f, sizeof(w)); for (int i = 1; i <= m; i++) { scanf("%d%d%d", &u, &v, &a); w[u][v] = max(-a, w[u][v]); } printf("%d\n", KM()); } return 0; }
URAL - 1099 Work Scheduling(带花树算法)
题意:
给一张图求最大匹配
思路:
由于一般图可能会有奇环,所以拆点后不一定是二分图,就得使用带花树算法
#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<vector> #include<cmath> #include<queue> #include<stack> #include<map> #include<set> #include<algorithm> using namespace std; const int maxn=300; int N; bool G[maxn][maxn]; int match[maxn]; bool InQueue[maxn],InPath[maxn],InBlossom[maxn]; int head,tail; int Queue[maxn]; int start,finish; int NewBase; int father[maxn],Base[maxn]; int Count; void CreateGraph(){ int u,v; memset(G,0,sizeof(G)); scanf("%d",&N); while(scanf("%d%d",&u,&v)!=EOF){ G[u][v]=G[v][u]=1; } } void Push(int u){ Queue[tail++]=u; InQueue[u]=1; } int Pop(){ int res=Queue[head++]; return res; } int FindCommonAncestor(int u,int v){ memset(InPath,0,sizeof(InPath)); while(true){ u=Base[u]; InPath[u]=1; if(u==start)break; u=father[match[u]]; } while(true){ v=Base[v]; if(InPath[v])break; v=father[match[v]]; } return v; } void ResetTrace(int u){ int v; while(Base[u]!=NewBase){ v=match[u]; InBlossom[Base[u]]=InBlossom[Base[v]]=1; u=father[v]; if(Base[u]!=NewBase)father[u]=v; } } void BlossomContract(int u,int v){ NewBase=FindCommonAncestor(u,v); memset(InBlossom,0,sizeof(InBlossom)); ResetTrace(u); ResetTrace(v); if(Base[u]!=NewBase)father[u]=v; if(Base[v]!=NewBase)father[v]=u; for(int tu=1;tu<=N;tu++){ if(InBlossom[Base[tu]]){ Base[tu]=NewBase; if(!InQueue[tu])Push(tu); } } } void FindAugmentingPath(){ memset(InQueue,0,sizeof(InQueue)); memset(father,0,sizeof(father)); for(int i=1;i<=N;i++){ Base[i]=i; } head=tail=1; Push(start); finish=0; while(head<tail){ int u=Pop(); for(int v=1;v<=N;v++){ if(G[u][v]&&(Base[u]!=Base[v])&&match[u]!=v){ if((v==start)||(match[v]>0)&&father[match[v]]>0){ BlossomContract(u,v); } else if(father[v]==0){ father[v]=u; if(match[v]>0){ Push(match[v]); } else { finish=v; return; } } } } } } void AugmentPath(){ int u,v,w; u=finish; while(u>0){ v=father[u]; w=match[v]; match[v]=u; match[u]=v; u=w; } } void Edmonds(){ memset(match,0,sizeof(match)); for(int u=1;u<=N;u++){ if(match[u]==0){ start=u; FindAugmentingPath(); if(finish>0)AugmentPath(); } } } void PrintMatch(){ Count=0; for(int u=1;u<=N;u++){ if(match[u]>0)Count++; } printf("%d\n",Count); for(int u=1;u<=N;u++){ if(u<match[u]){ printf("%d %d\n",u,match[u]); } } } int main(){ CreateGraph(); Edmonds();//进行匹配 PrintMatch();//输出匹配 return 0; }
HDU 4687 Boke and Tsukkomi
题意:
某场比赛,希望有效匹配数尽量多,在此条件下,输出题目给出组合中,无效匹配的数目
思路:
一般图的最大匹配。我们可以先求出原图所有边的匹配数cnt,然后删除这条边之后的匹配数tcnt,如果tcnt + 1 == cnt。那么说明这条边是有效的匹配,否则加入答案
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> #include <cmath> #include <queue> #include <utility> #include <functional> #include <string> #include <cctype> #include <list> using namespace std; const int MAXN = 300; int N, M; //点的个数,点的编号从1到N bool Graph[MAXN][MAXN], g[MAXN][MAXN]; int Match[MAXN]; bool InQueue[MAXN], InPath[MAXN], InBlossom[MAXN]; int Head, Tail; int Queue[MAXN]; int Start, Finish; int NewBase; int Father[MAXN], Base[MAXN]; int Count;//匹配数,匹配对数是Count/2 pair <int, int> p[MAXN]; void CreateGraph() { memset(Graph, false, sizeof(Graph)); memset(g, false, sizeof(g)); for (int i = 1; i <= M; i++) { int u, v; scanf("%d%d", &u, &v); Graph[u][v] = Graph[v][u] = true; g[u][v] = g[v][u] = true; p[i].first = u, p[i].second = v; } } void Push(int u) { Queue[Tail] = u; Tail++; InQueue[u] = true; } int Pop() { int res = Queue[Head]; Head++; return res; } int FindCommonAncestor(int u, int v) { memset(InPath, false, sizeof(InPath)); while (true) { u = Base[u]; InPath[u] = true; if (u == Start) break; u = Father[Match[u]]; } while (true) { v = Base[v]; if (InPath[v])break; v = Father[Match[v]]; } return v; } void ResetTrace(int u) { int v; while (Base[u] != NewBase) { v = Match[u]; InBlossom[Base[u]] = InBlossom[Base[v]] = true; u = Father[v]; if (Base[u] != NewBase) Father[u] = v; } } void BloosomContract(int u, int v) { NewBase = FindCommonAncestor(u, v); memset(InBlossom, false, sizeof(InBlossom)); ResetTrace(u); ResetTrace(v); if (Base[u] != NewBase) Father[u] = v; if (Base[v] != NewBase) Father[v] = u; for (int tu = 1; tu <= N; tu++) if (InBlossom[Base[tu]]) { Base[tu] = NewBase; if (!InQueue[tu]) Push(tu); } } void FindAugmentingPath() { memset(InQueue, false, sizeof(InQueue)); memset(Father, 0, sizeof(Father)); for (int i = 1; i <= N; i++) Base[i] = i; Head = Tail = 1; Push(Start); Finish = 0; while (Head < Tail) { int u = Pop(); for (int v = 1; v <= N; v++) if (Graph[u][v] && (Base[u] != Base[v]) && (Match[u] != v)) { if ((v == Start) || ((Match[v] > 0) && Father[Match[v]] > 0)) BloosomContract(u, v); else if (Father[v] == 0) { Father[v] = u; if (Match[v] > 0) Push(Match[v]); else { Finish = v; return; } } } } } void AugmentPath() { int u, v, w; u = Finish; while (u > 0) { v = Father[u]; w = Match[v]; Match[v] = u; Match[u] = v; u = w; } } void Edmonds() { memset(Match, 0, sizeof(Match)); for (int u = 1; u <= N; u++) if (Match[u] == 0) { Start = u; FindAugmentingPath(); if (Finish > 0) AugmentPath(); } } int PrintMatch() { Edmonds(); Count = 0; for (int u = 1; u <= N; u++) if (Match[u] > 0) Count++; return Count / 2; } vector <int> pnt; int main() { while (~scanf("%d%d", &N, &M)) { pnt.clear(); CreateGraph(); int sum = PrintMatch(); for (int i = 1; i <= M; i++) { int u = p[i].first, v = p[i].second; memcpy(Graph, g, sizeof(g)); for (int j = 1; j <= N; j++) Graph[u][j] = Graph[j][u] = Graph[v][j] = Graph[j][v] = false; int res = PrintMatch(); if (res < sum - 1) pnt.push_back(i); } int cnt = pnt.size(); printf("%d\n", cnt); for (int i = 0; i < cnt; i++) { if (i != cnt - 1) printf("%d ", pnt[i]); else printf("%d", pnt[i]); } printf("\n"); } return 0; }