http://poj.org/problem?id=2195

对km算法不理解,模板用的也不好。

下面是大神的解释。

KM算法的要点是在相等子图中寻找完备匹配,其正确性的基石是:任何一个匹配的权值之和都不大于所有顶点的顶标之和,而能够取到相等的必然是最大权匹配。
左右两边点数不等时,KM算法的正确性也是可以得到保证的。原因如下:
由KM算法中可行点标的定义,有:
任意匹配的权值 <= 该匹配所覆盖的所有点的顶标值 <= KM算法所得到的匹配所覆盖的所有点的顶标值 = KM算法所得到的的匹配的权值 上面的证明与网上大多数证明不同点在于,网上的证明中只是模糊地说“图中所有点的顶标值”,这只在两点集点数相等时才正确。 上面第二个不等号最为关键,它的理由是:假定|X|<=|Y|,则任意时刻,Y集合中KM算法所取的匹配未覆盖的点的顶标必为0!
 #include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std; const int maxn = + ; const int INF = 0x7fffffff; int n,m;
int W[maxn][maxn]; //存储权值
int Lx[maxn], Ly[maxn]; // 顶标
int left[maxn]; // left[i]为右边第i个点的匹配点编号
bool S[maxn], T[maxn]; // S[i]和T[i]为左/右第i个点是否已标记 bool match(int i){
S[i] = true;
for(int j = ; j <= m; j++) if (Lx[i]+Ly[j]==W[i][j] && !T[j])
{
T[j] = true;
if (!left[j] || match(left[j]))
{
left[j] = i;
return true;
}
}
return false;
} void update(){
int a = INF;
for(int i = ; i <= n; i++) if(S[i])
for(int j = ; j <= m; j++) if(!T[j])
a = min(a, W[i][j]-Lx[i]-Ly[j]); //若是最大权匹配则Lx[i]+Ly[j]-W[i][j]; 同样是取最小值
for(int i = ; i <= n; i++) {
if(S[i]) Lx[i] -= a;
if(T[i]) Ly[i] += a;
}
} void KM() {
for(int i = ; i <= n; i++) {
left[i] = Lx[i] = Ly[i] = ;
for(int j = ; j <= m; j++)
Lx[i] = min(Lx[i], W[i][j]); //若是最大权匹配.则初始值顶标取最大值,最小匹配则去最小值
}
for(int i = ; i <= n; i++) {
for(;;) {
for(int j = ; j <= m; j++) S[j] = T[j] = ;
if(match(i)) break; else update();
}
}
}
char s[];
struct point
{
int x,y;
};
point man[],house[];
int main(){
//freopen("a.txt","r",stdin);
int sum,man_num,house_num;
while(scanf("%d%d",&n,&m)!=EOF&&(n+m))
{
int i,j;
man_num=,house_num=;
for(int i=;i<=n;i++)
{
scanf("%s",s+);
for(int j=;j<=m;j++)
{
if(s[j]=='m')
{
man[man_num].x=i;
man[man_num++].y=j;
}
else if(s[j]=='H')
{
house[house_num].x=i;
house[house_num++].y=j;
}
}
}
man_num--,house_num--;
// printf("%d %d\n",man_num,house_num);
n=man_num;m=house_num;
for(int i=;i<=man_num;i++)
for(int j=;j<=house_num;j++)
W[i][j]=abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y);
sum=;
KM(); // 最大权匹配
for(i=;i<=m;i++)
sum+=W[left[i]][i];
printf("%d\n",sum);
}
//for(int i = 1; i <= n; i++) printf("left[%d]=%d\n", i,left[i]);
//getch();
return ;
}

当然还可以用费用流来做.

算出每一个到每一个房子的距离后建图。
源点与人连,容量1,费用0
人与每个房子都要连,容量1,费用为距离
每个房子与汇点连,容量1,费用0
求一次最小费用即可
另外此题数据的范围开大点。
Bellmanford:
 #include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#include<cassert>
using namespace std; const int maxn = + ;
const int INF = ; struct Edge
{
int from, to, cap, flow, cost;
Edge(int u, int v, int c, int f, int w):from(u),to(v),cap(c),flow(f),cost(w) {}
}; struct MCMF
{
int n, m;
vector<Edge> edges;
vector<int> G[maxn];
int inq[maxn]; // 是否在队列中
int d[maxn]; // Bellman-Ford
int p[maxn]; // 上一条弧
int a[maxn]; // 可改进量 void init(int n)
{
this->n = n;
for(int i = ; i < n; i++) G[i].clear();
edges.clear();
} void AddEdge(int from, int to, int cap, int cost)
{
edges.push_back(Edge(from, to, cap, , cost));
edges.push_back(Edge(to, from, , , -cost));
m = edges.size();
G[from].push_back(m-);
G[to].push_back(m-);
} bool BellmanFord(int s, int t, int flow_limit, int& flow, int& cost)
{
for(int i = ; i < n; i++) d[i] = INF;
memset(inq, , sizeof(inq));
d[s] = ; inq[s] = ; p[s] = ; a[s] = INF; queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front(); Q.pop();
inq[u] = ;
for(int i = ; i < G[u].size(); i++)
{
Edge& e = edges[G[u][i]];
if(e.cap > e.flow && d[e.to] > d[u] + e.cost)
{
d[e.to] = d[u] + e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u], e.cap - e.flow);
if(!inq[e.to]) { Q.push(e.to); inq[e.to] = ; }
}
}
}
if(d[t] == INF) return false;
if(flow + a[t] > flow_limit) a[t] = flow_limit - flow;
flow += a[t];
cost += d[t] * a[t];
for(int u = t; u != s; u = edges[p[u]].from)
{
edges[p[u]].flow += a[t];
edges[p[u]^].flow -= a[t];
}
return true;
} // 需要保证初始网络中没有负权圈
int MincostFlow(int s, int t, int flow_limit, int& cost)
{
int flow = ; cost = ;
while(flow < flow_limit && BellmanFord(s, t, flow_limit, flow, cost));
return flow;
} }; MCMF g;
typedef struct{
int x,y;
}Point; Point man[maxn],home[maxn];
int man_sum,home_sum; int main()
{
int n,m,i,j;
char s[maxn];
while(~scanf("%d%d",&n,&m) && n || m)
{
getchar();
man_sum=home_sum=;
for(i=;i<=n;i++)
{ //记录下人与房子的坐标
gets(s);
for(j=;j<m;j++)
{
if(s[j]=='m')
{
man[man_sum].x=i;
man[man_sum++].y=j+;
}
if(s[j]=='H')
{
home[home_sum].x=i;
home[home_sum++].y=j+;
}
}
}
man_sum--; home_sum--;
g.init(man_sum+home_sum+); for(i=;i<=man_sum;i++) g.AddEdge(, i, , ); //源点指向人 for(i=;i<=man_sum;i++) //人指向房子
{
for(j=man_sum+;j<=man_sum+home_sum;j++)
{
g.AddEdge(i, j, , abs(man[i].x-home[j-man_sum].x)+abs(man[i].y-home[j-man_sum].y));
}
} for(i=man_sum+;i<=man_sum+home_sum;i++) g.AddEdge(i, man_sum+home_sum+, , ); //房子指向汇点 int cost;
g.MincostFlow(, man_sum+home_sum+, man_sum, cost);
printf("%d\n", cost);
}
return ;
}

SPFA:

参考:http://blog.csdn.net/lenleaves/article/details/7904588

 #include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<cmath>
#include<cstdio> using namespace std; #define MAXN 2000
#define MAXM 110000
#define INF 0x3FFFFFF struct edge
{
int to,c,w,next;
}; edge e[MAXM];
bool in[MAXN];
int hx[],hy[],mx[],my[];
int head[MAXN],dis[MAXN],pre[MAXN],en,maxflow,mincost;
int vn,st,ed,r,c,hn,mn; void add(int a,int b,int c,int d)
{
e[en].to=b;
e[en].c=c;
e[en].w=d;
e[en].next=head[a];
head[a]=en++;
e[en].to=a;
e[en].c=;
e[en].w=-d;
e[en].next=head[b];
head[b]=en++;
} bool spfa(int s)
{
queue<int> q;
for(int i=;i<=vn;i++)
{
dis[i]=INF;
in[i]=false;
pre[i]=-;
}
dis[s]=;
in[s]=true;
q.push(s);
while(!q.empty())
{
int tag=q.front();
in[tag]=false;
q.pop();
for(int i=head[tag];i!=-;i=e[i].next)
{
int j=e[i].to;
if(e[i].w+dis[tag]<dis[j] && e[i].c)
{
dis[j]=e[i].w+dis[tag];
pre[j]=i;
if(!in[j])
{
q.push(j);
in[j]=true;
}
}
}
}
if(dis[ed]==INF)
return false;
return true;
} void update()
{
int flow=INF;
for (int i=pre[ed];i!=-;i=pre[e[i^].to])
if(e[i].c<flow) flow=e[i].c;
for (int i=pre[ed];i!=-;i=pre[e[i^].to])
{
e[i].c-=flow,e[i^].c+=flow;
}
maxflow+=flow;
mincost+=flow*dis[ed];
} void mincostmaxflow()
{
maxflow=,mincost=;
while(spfa(st))
update();
} void solve()
{
char s[];
hn=,mn=,en=;
memset(head,-,sizeof(head));
for(int i=;i<=r;i++)
{
scanf("%s",s);
for(int j=;j<c;j++)
{
if(s[j]=='m') mx[mn]=i,my[mn++]=j+;
if(s[j]=='H') hx[hn]=i,hy[hn++]=j+;
}
}
vn=mn+hn+;
st=,ed=mn+hn+;
for(int i=;i<mn;i++)
for(int j=;j<hn;j++)
{
int dist=abs(mx[i]-hx[j])+abs(my[i]-hy[j]);
add(i+,j++mn,,dist);
}
for(int i=;i<mn;i++)
add(st,i+,,);
for(int i=;i<hn;i++)
add(i++mn,ed,,);
mincostmaxflow();
printf("%d\n",mincost);
} int main()
{
while(scanf("%d%d",&r,&c)!=EOF && r+c)
solve();
return ;
}

poj - 2195 Going Home (费用流 || 最佳匹配)的更多相关文章

  1. POJ 2195 Going Home (费用流)

    题面 On a grid map there are n little men and n houses. In each unit time, every little man can move o ...

  2. POJ 2175 Evacuation Plan (费用流,负环,消圈法,SPFA)

    http://poj.org/problem?id=2175 Evacuation Plan Time Limit: 1000MS   Memory Limit: 65536K Total Submi ...

  3. POJ 2516 Minimum Cost (费用流)

    题面 Dearboy, a goods victualer, now comes to a big problem, and he needs your help. In his sale area ...

  4. Going Home POJ - 2195 (最小费用最大流)

    On a grid map there are n little men and n houses. In each unit time, every little man can move one ...

  5. POJ 3680 Intervals(费用流)

    Intervals Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 5762   Accepted: 2288 Descrip ...

  6. POJ 2175 Evacuation Plan 费用流 负圈定理

    题目给了一个满足最大流的残量网络,判断是否费用最小. 如果残量网络中存在费用负圈,那么不是最优,在这个圈上增广,增广1的流量就行了. 1.SPFA中某个点入队超过n次,说明存在负环,但是这个点不一定在 ...

  7. POJ 3680 Intervals(费用流+负权优化)

    [题目链接] http://poj.org/problem?id=3680 [题目大意] 有N个带权重的区间,现在要从中选取一些区间, 要求任意点都不被超过K个区间所覆盖,请最大化总的区间权重. [题 ...

  8. poj 2135 Farm Tour 费用流

    题目链接 给一个图, N个点, m条边, 每条边有权值, 从1走到n, 然后从n走到1, 一条路不能走两次,求最短路径. 如果(u, v)之间有边, 那么加边(u, v, 1, val), (v, u ...

  9. BZOJ3502PA2012Tanie linie&BZOJ2288[POJ Challenge]生日礼物——模拟费用流+链表+堆

    题目描述 n个数字,求不相交的总和最大的最多k个连续子序列. 1<= k<= N<= 1000000. 输入 输出 样例输入 5 2 7 -3 4 -9 5 样例输出 13   根据 ...

随机推荐

  1. BootStrap Select2组件

    想使用Select2组件必须引用:select2.min.css和select2.min.js两个文件:如下: 页面写法很简单: 在这里多选是没有搜索功能的,只有单选的时候才会有搜索功能. Selec ...

  2. HttpWebRequest 以及WebRequest的使用

    1.WebRequest的发送数据以及接收数据 class Program { static void Main(string[] args) { //创建一个实例并发送请求 HttpWebReque ...

  3. java web 学习笔记 - servlet02

    1.servlet的跳转 客户端跳转: 通过doget函数中的response参数调用resp.sendRedirect(url); 代码如下 protected void doGet(HttpSer ...

  4. leetcode_357. Count Numbers with Unique Digits

    https://leetcode.com/problems/count-numbers-with-unique-digits/ 给定一个n,计算[0,10^n]中十进制中每一位都不相同的数的数目. c ...

  5. Winform窗体验证登陆

    用户名,密码尽量不要在BLL,UIL判断,尽可能的在储存过程判断,通过返回的值不同,进行判断,这样提高安全性SQL Server储存过程代码: BEGINif(exists ( select User ...

  6. CSS 文字换行与不换行

    1. 强制不换行 p{ white-space:nowrap; } 2. 自动换行 p{ word-wrap: break-word; word-break: normal; } 3. 强制英文单词断 ...

  7. css 样式渲染

     1.文字过长时,自动换行

  8. 6-Java-C(无穷分数)

    题目描述: 无穷的分数,有时会趋向于固定的数字. 请计算[图1.jpg]所示的无穷分数,要求四舍五入,精确到小数点后5位,小数位不足的补0. 请填写该浮点数,不能填写任何多余的内容. 正确算法: 此题 ...

  9. CAD交互绘制样条线(com接口)

    在CAD设计时,需要绘制样条线,用户可以设置样条线线重及颜色等属性. 主要用到函数说明: _DMxDrawX::SendStringToExecuteFun 把命令当着函数执行,可以传参数.详细说明如 ...

  10. CAD参数绘制填充(网页版)

    填充是CAD图纸中不可或缺的对象,在机械设计行业,常常需要将零部件剖开,以表现其内部的细节,而这些被剖开的截面会用填充来表示:在工程设计行业,一些特殊的材料或地形,也会用填充来表示. js中实现代码说 ...