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;
}