【算法】POJ2195 Going Home

来源:互联网 发布:淘宝会查物流重量吗 编辑:程序博客网 时间:2024/06/05 12:07

题目:Going Home
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 22134 Accepted: 11177

Description
On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, either horizontally, or vertically, to an adjacent point. For each little man, you need to pay a $1 travel fee for every step he moves, until he enters a house. The task is complicated with the restriction that each house can accommodate only one little man.

Your task is to compute the minimum amount of money you need to pay in order to send these n little men into those n different houses. The input is a map of the scenario, a ‘.’ means an empty space, an ‘H’ represents a house on that point, and am ‘m’ indicates there is a little man on that point.

You can think of each point on the grid map as a quite large square, so it can hold n little men at the same time; also, it is okay if a little man steps on a grid with a house without entering that house.

Input
There are one or more test cases in the input. Each case starts with a line giving two integers N and M, where N is the number of rows of the map, and M is the number of columns. The rest of the input will be N lines describing the map. You may assume both N and M are between 2 and 100, inclusive. There will be the same number of ‘H’s and ‘m’s on the map; and there will be at most 100 houses. Input will terminate with 0 0 for N and M.

Output
For each test case, output one line with the single integer, which is the minimum amount, in dollars, you need to pay.

Sample Input

2 2
.m
H.
5 5
HH..m
…..
…..
…..
mm..H
7 8
…H….
…H….
…H….
mmmHmmmm
…H….
…H….
…H….
0 0

Sample Output

2
10
28

找了很多代码,都没有解释的很详细
贴下AC的代码,注释比较详细。其中
1.图的存储按边存储,用了邻接表的前向星存储
2.nume一定要在加边前置为1,这是和后面的相呼应
e[pree[u] ^ 1].f += delta;
//pree[u]^1,pree[u]是指向u这个点的边的编号,与1异或之后,奇数减1,偶数加1,反向边的容量加delta。
即给第一条边设编号为2,第二条边编号为3.那么偶数条边就增加delta流量,奇数条边就减去delta流量
3.每条边只有四个属性,初始的容量即为此边上可以增加的最大流量。
每次改变流量时,如果这条边增加流量,那么就减少此边的容量。
反向边的容量增加为delta,这点很巧妙,避免了用f,c,c-f三个变量表示,一个变量即可解决。
4.加边时加了一条正向弧和一条反向弧,但是反向弧的容量为0,在findPath时有个判断条件e[i].f > 0,容量为0的边并没有考虑。

#include<iostream>  #include<cstring>  #include<cstdlib>  #include<cstdio>  #include<climits>  #include<algorithm>  #include<queue>  #include<vector>using namespace std;const int maxn = 5000;//N=n*m+2const int maxm = 50000;//M=N*N+2*Nconst int inf = 0xffffff;struct Edge{    Edge(){};    Edge(int a, int b, int c, int d){        v = a; f = b; w = c; nxt = d;    };    int v, f, w, nxt;    //v 容量  f流   w价值  };int n, m, lmt;//m行n列int g[maxn + 10];//存储顶点的最后一条边Edge e[maxm + 10];//边集int nume;int src, sink;//src是源点,sink是汇点int flow; //定义全局变量flow表示流量int tol;int vertex;//顶点数 int grid = 200;//每个网格的边长int Rc = 300;//连通半径char coord[maxn][maxn];//坐标集  //加边操作,表示加一条u到v容量为c费用为w的边,请务必在开始之前把nume赋值成1,并把g数组赋值为0.void addedge(int u, int v, int c, int w){    e[++nume] = Edge(v, c, w, g[u]);//这条边指向顶点v,容量为c,权重为w,nxt是g[u],是u的邻接点    g[u] = nume;    e[++nume] = Edge(u, 0, -w, g[v]);//这条边指向顶点u,容量为0,权重为-w,nxt是g[v],是v的邻接点    g[v] = nume;}queue<int>que;bool inQue[maxn + 10];//记录是否在队列中int dist[maxn + 10];//记录最短距离int pre[maxn + 10];//记录前驱结点int pree[maxn + 10];//记录到达的顶点对应的边的编号struct man//记录小矮人的坐标  {    int x, y;} man[maxn];struct house//记录房子的坐标  {    int x, y;} house[maxn];void init(){    memset(g, 0, sizeof(g));    nume = 1;    flow=0;    int mcase, hcase;//记录有多少个小矮人和房子      mcase = hcase = 0;    for (int i = 1; i <= m; i++)    {        for (int j = 1; j <= n; j++)        {            cin >> coord[i][j];            if (coord[i][j] == 'm')//记录小矮人的坐标              {                mcase++;                man[mcase].x = i;                man[mcase].y = j;            }            if (coord[i][j] == 'H')//记录房子的坐标              {                hcase++;                house[hcase].x = i;                house[hcase].y = j;            }        }    }    vertex = mcase + hcase + 1;//加入超源点0和超汇点,注意要+1,即抽象成网络流的结构      src = 0, sink = vertex;    for (int i = 1; i <= mcase; i++)    {        addedge(src, i, 1, 0);//加从源点到小矮人之间的边,容量为1,费用为0          for (int j = 1; j <= hcase; j++)        {            int w = abs(house[j].x - man[i].x) + abs(house[j].y - man[i].y);//计算小矮人到每一个房子之间的距离              //cout << i << "与" << j << "之间的距离是" << w << endl;            addedge(i, mcase + j, 1, w);//将距离赋给对应的权值,注意第二个下标,即表示房子的下标为mcase+j~!!  容量取为1          }    }    for (int j = 1; j <= hcase; j++)    {        addedge(mcase + j, sink, 1, 0);//加从房子到汇点之间的边,容量为1,费用为0    }}bool findPath()//类似SPFA,找到一条最短路径{    while (!que.empty())        que.pop(); //将队列清空    que.push(src); //将源点入队    for (int i = 0; i < maxn+10; i++)//初始化    {        dist[i] = inf;        inQue[i] = false;        pre[i] = -1;    }    dist[src] = 0; //将源点的距离置为0    inQue[src] = true;//入队标志为真,表示在队列中    while (!que.empty())    {        int u = que.front();//队首元素赋给u        que.pop();  //取出队首元素        for (int i = g[u]; i; i = e[i].nxt)//更新u的邻接点的dist[], pre[], inq[],从最后一条边向前找        {            if (e[i].f > 0 && dist[u] + e[i].w < dist[e[i].v])//这条边的容量大于0,即可以增流而且距离满足条件            {                dist[e[i].v] = dist[u] + e[i].w;//更新距离                pre[e[i].v] = u;//e[i].v这个点的前驱结点是u                pree[e[i].v] = i;//给e[i].v这个点对应的边的边号                if (!inQue[e[i].v])//将e[i].v这个点的入队标志设为true                {                    inQue[e[i].v] = true;                    que.push(e[i].v); //将e[i].v这个点入队                }            }        }        inQue[u] = false;//u结点检查完,将入队标志设为false    }    if (pre[sink] == -1)        return false;    else         return true;}int augment(){    int u = sink;    int delta = inf;    while (u != src){        if (e[pree[u]].f < delta)            delta = e[pree[u]].f;//找到最短路径上可以增的最大的容量,即每条边的容量的最小值        u = pre[u];    }    flow += delta;//把增加的流量delta加到flow上    u = sink;    while (u != src)    {        e[pree[u]].f -= delta;//f就是容量c,增流delta和容量c减少delta是一个结果。可以省去f这个变量,只用一个变量f即可表示可增的流量        e[pree[u] ^ 1].f += delta;//pree[u]^1,与1异或之后,奇数减1,偶数加1,反向边的容量加delta        u = pre[u];    }    //cout << "delta:" << delta << endl;    //cout << "dist[sink]:" << dist[sink] << endl;    return dist[sink] * delta;}//输入:src,sink 全局变量,表示源点和汇点    //  g,e     全局变量,表示存边的邻接表//输出:最小的费用int mincostflow(){    int cur = 0;    while (findPath()){        cur += augment();//augment中是每次增广路上增加的费用    }    //cout << "cur:" << cur << endl;    return cur;}int main(){    //freopen("C:\\Users\\Administrator\\Desktop\\kd.txt","r",stdin);         while (cin >> m >> n, m || n)     {         init();         int tol = mincostflow();//计算从超源点0到超汇点vertex之间的最小费用最大流           //cout << "flow:"<<flow << endl;         cout << "tol:" << tol << endl;     }    return 0;}

参考
1.《ACM国际大学生程序设计竞赛:算法与实现》
2.存储结构参考:
http://www.cnblogs.com/NoSoul/archive/2012/04/02/2429958.html
http://tieba.baidu.com/p/1257811433
3.http://www.cnblogs.com/NoSoul/archive/2012/04/02/2429958.html

0 0