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

任何问题 都是 难了不会 会了不难 难就难在由不会变成会

尤其是刚接触到一个新知识点的时候硬着头皮,耐心地去看去理解,一定能学会,然后你就会发现它

原来并不难

本题是一个最小费用流

详解见代码注释

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdio>

using namespace std;
const int N=1001;//后台数据水了 开1000就过了 其实更小也过不过也不能太小 其实理论上是10000
const int M=100000005;
int flow[N][N];//保存流
int pay[N][N];//保存费用
struct node
{
    int x,y;
    char c;
}mem[10010];//保存输入的m点和H点

void find_flow_pay(int i,int j)
{
   if(mem[i].c==mem[j].c)//两个点必须一个是m一个是H
   return ;
   if(mem[i].c=='m')
   {
       pay[i][j]=abs(mem[j].x-mem[i].x)+abs(mem[j].y-mem[i].y);//求费用
       pay[j][i]=-pay[i][j];//反向费用变负的
       flow[i][j]=1;//正向流为1 反向为0
   }
   else//同上 只不过这个 j点为m而已
   {
       pay[j][i]=abs(mem[j].x-mem[i].x)+abs(mem[j].y-mem[i].y);
       pay[i][j]=-pay[j][i];
       flow[j][i]=1;
   }
}
int Spfa(int n)
{
    bool in[N];//是否在队列中
    int dist[N];//距离
    int f[N];//前驱点
    memset(in,false,sizeof(in));
    for(int i=1;i<=n;++i)
    dist[i]=M;//初始化最大
    dist[0]=0;//超级源点为0
    queue<int>str;
    str.push(0);
    in[0]=true;
    while(!str.empty())//由于没有负环 所以直接找到队列为空就开
    {
        int x=str.front();
        str.pop();
        in[x]=false;
        for(int i=1;i<=n;++i)
        {
            if(flow[x][i]>0&&dist[x ]+pay[x][i]<dist[i])
            {
                dist[i]=dist[x]+pay[x][i];//更新距离 其实就是费用
                f[i]=x;//标记前驱
                if(in[i]==false)//当i不在队列中就进队列 并标记
                {
                    in[i]=true;
                    str.push(i);
                }
            }
        }
    }
    if(dist[n]==M)//到达不了超级终端则返回0
    return 0;
    int k=n;
    while(k!=0)//更新流
    {
        int pre=f[k];
        --flow[pre][k];
        ++flow[k][pre];
        k=pre;
    }
    return dist[n];

}
int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        if(n==0&&m==0)
        break;
        int I=1;
        char ctemp;
        memset(flow,0,sizeof(flow));
        memset(pay,0,sizeof(pay));
        for(int i=1;i<=n;++i)
        {
            getchar();//吃掉回车
            for(int j=1;j<=m;++j)
            {
                scanf("%c",&ctemp);
                if(ctemp!='.')
                {
                    mem[I].c=ctemp;mem[I].x=i;mem[I].y=j;
                    for(int l=1;l<I;++l)
                    {
                        find_flow_pay(l,I);//球两点的流和花费
                    }
                    ++I;
                }
            }
        }
        for(int i=1;i<I;++i)//0 为超级源点 I 为超级终点
        {
            if(mem[i].c=='m')
            {
                flow[0][i]=1;
            }
            else
            {
                flow[i][I]=1;
            }
        }
        int ans=0;
        while(1)
        {
           int k=Spfa(I);
           if(k==0)//无流更新
           break;
           ans=ans+k;
        }
        cout<<ans<<endl;
    }
    return 0;
}

 

posted on 2012-05-24 19:43  夜->  阅读(164)  评论(0编辑  收藏  举报