二分图多重匹配(改网络流源点到x部weight=1,y部到汇点为最大匹配数,x部和y部能通过则为INF,不能通过为0)
对于Y部中的元素,可以根据设定的限制与X部匹配。
经典题目:http://blog.csdn.net/u014665013/article/details/51344918
const int MAXN = 1010; const int MAXM = 510; int uN,vN; int g[MAXN][MAXN]; int linker[MAXM][MAXN]; bool vis[MAXN]; int num[MAXM];///右边最大的匹配数 bool dfs(int u) { for(int v=0; v<vN; v++) if(g[u][v] && !vis[v]) { vis[v]=true; if(linker[v][0] < num[v]){ linker[v][++linker[v][0]] = u; return true; } for(int i=1; i <= num[v] ; i++) if(dfs(linker[v][i])){ linker[v][i] = u; return true; } } return false; } int hungary(){ int res = 0; for(int i=0;i<vN;i++) linker[i][0]=0; for(int u=0;u<uN;u++){ memset(vis,false,sizeof(vis)); if(dfs(u)) res++; } return res; }
每个匹配都有加上权值,在原来匹配的基础上,求权值最大的匹配。
经典例题:http://blog.csdn.net/u014665013/article/details/51346811
模板:
#include <stdio.h> #include <algorithm> #include <string.h> #include <iostream> using namespace std; /* KM算法 * 复杂度O(nx*nx*ny) * 求最大权匹配 * 若求最小权匹配,可将权值取相反数,结果取相反数 * 点的编号从0开始 */ const int N = 310; const int INF = 0x3f3f3f3f; int nx,ny;//两边的点数 int g[N][N];//二分图描述 int linker[N],lx[N],ly[N];//y中各点匹配状态,x,y中的点标号 int slack[N]; bool visx[N],visy[N]; bool DFS(int x) { visx[x] = true; for(int y = 0; y < ny; y++) { if(visy[y])continue; int tmp = lx[x] + ly[y] - g[x][y]; if(tmp == 0) { visy[y] = true; if(linker[y] == -1 || DFS(linker[y])) { linker[y] = x; return true; } } else if(slack[y] > tmp) slack[y] = tmp; } return false; } int KM() { memset(linker,-1,sizeof(linker)); memset(ly,0,sizeof(ly)); for(int i = 0;i < nx;i++) { lx[i] = -INF; for(int j = 0;j < ny;j++) if(g[i][j] > lx[i]) lx[i] = g[i][j]; } for(int x = 0;x < nx;x++) { for(int i = 0;i < ny;i++) slack[i] = INF; while(true) { memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if(DFS(x))break; int d = INF; for(int i = 0;i < ny;i++) if(!visy[i] && d > slack[i]) d = slack[i]; for(int i = 0;i < nx;i++) if(visx[i]) lx[i] -= d; for(int i = 0;i < ny;i++) { if(visy[i])ly[i] += d; else slack[i] -= d; } } } int res = 0; for(int i = 0;i < ny;i++) if(linker[i] != -1) res += g[linker[i]][i]; return res; } //HDU 2255 int main() { int n; while(scanf("%d",&n) == 1) { for(int i = 0;i < n;i++) for(int j = 0;j < n;j++) scanf("%d",&g[i][j]); nx = ny = n; printf("%d\n",KM()); } return 0; }
另一个版本:
#include <iostream> #include <cstdio> #include <memory.h> #include <algorithm> using namespace std; #define MAX 100 int n; int weight[MAX][MAX]; //权重 int lx[MAX],ly[MAX]; //定点标号 bool sx[MAX],sy[MAX]; //记录寻找增广路时点集x,y里的点是否搜索过 int match[MAX]; //match[i]记录y[i]与x[match[i]]相对应 bool search_path(int u) { //给x[u]找匹配,这个过程和匈牙利匹配是一样的 sx[u]=true; for(int v=0; v<n; v++){ if(!sy[v] &&lx[u]+ly[v] == weight[u][v]){ sy[v]=true; if(match[v]==-1 || search_path(match[v])){ match[v]=u; return true; } } } return false; } int Kuhn_Munkras(bool max_weight){ if(!max_weight){ //如果求最小匹配,则要将边权取反 for(int i=0;i<n;i++) for(int j=0;j<n;j++) weight[i][j]=-weight[i][j]; } //初始化顶标,lx[i]设置为max(weight[i][j] | j=0,..,n-1 ), ly[i]=0; for(int i=0;i<n;i++){ ly[i]=0; lx[i]=-0x7fffffff; for(int j=0;j<n;j++) if(lx[i]<weight[i][j]) lx[i]=weight[i][j]; } memset(match,-1,sizeof(match)); //不断修改顶标,直到找到完备匹配或完美匹配 for(int u=0;u<n;u++){ //为x里的每一个点找匹配 while(1){ memset(sx,0,sizeof(sx)); memset(sy,0,sizeof(sy)); if(search_path(u)) //x[u]在相等子图找到了匹配,继续为下一个点找匹配 break; //如果在相等子图里没有找到匹配,就修改顶标,直到找到匹配为止 //首先找到修改顶标时的增量inc, min(lx[i] + ly [i] - weight[i][j],inc);,lx[i]为搜索过的点,ly[i]是未搜索过的点,因为现在是要给u找匹配,所以只需要修改找的过程中搜索过的点,增加有可能对u有帮助的边 int inc=0x7fffffff; for(int i=0;i<n;i++) if(sx[i]) for(int j=0;j<n;j++) if(!sy[j]&&((lx[i] + ly [j] - weight[i][j] )<inc)) inc = lx[i] + ly [j] - weight[i][j] ; //找到增量后修改顶标,因为sx[i]与sy[j]都为真,则必然符合lx[i] + ly [j] =weight[i][j],然后将lx[i]减inc,ly[j]加inc不会改变等式,但是原来lx[i] + ly [j] !=weight[i][j]即sx[i]与sy[j]最多一个为真,lx[i] + ly [j] 就会发生改变,从而符合等式,边也就加入到相等子图中 if(inc==0) cout<<"fuck!"<<endl; for(int i=0;i<n;i++){ if(sx[i]) // lx[i]-=inc; if(sy[i]) ly[i]+=inc; } } } int sum=0; for(int i=0;i<n;i++) if(match[i]>=0) sum+=weight[match[i]][i]; if(!max_weight) sum=-sum; return sum; } int main(){ scanf("%d",&n); for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&weight[i][j]); printf("%d\n",Kuhn_Munkras(1)); system("pause"); return 0; }
最小点权覆盖(覆盖所有边)(部分可转网络流,例如:http://blog.csdn.net/u014665013/article/details/50082537)
二分图是完备匹配的条件下,连接二分图中的点,每个点都有权值,求最小的点权值
通过转化为最大权匹配(转化为最小权只需把权值前面加上负号)