原题地址:点击打开链接
题意:给你一个矩阵,每个点上都有一个数字代表需要被救的人数,你从(1,1)点出发开始救人走过的点不能再走,不过你只能向右或者向下走,到达终点后(m,n)返回起点,只能向上或者左走,每路过一个点就可以救该点有的人数,问你最多可以就多少人。
思路:可转化为最小费用流问题,关键的难点在于建图,该题需要拆点,每个点到自身的流量为1,(起点和终点的流量为2),然后根据顶点之间的可达性进行建边,最后注意结果要将起点和终点的人数减去一次,因为多加了一次。
#include<stdio.h>
#include<string.h>
#include<vector>
#define INF 1<<31-1
#define min(x,y)(x<y?x:y)
using namespace std;
struct Edge
{
int to;
int cap;
int rev;
int cost;
};
vector<Edge>g[20010];
void add_edge(int from,int to,int cap,int cost)
{
g[from].push_back((Edge){to,cap,g[to].size(),cost});
g[to].push_back((Edge){from,0,g[from].size()-1,-cost});
}
int id[110][110];
int prev[20010];
int pree[20010];
int dis[20010];
int min_cost_flow(int s,int t,int f)
{
int res=0,i,u,v;
bool update;
while(f>0)
{
update=true;
for(i=s;i<=t;i++)
dis[i]=-INF;
dis[s]=0;
while(update)
{
update=false;
for(u=1;u<=t;u++)
{
if(dis[u]==-INF)
continue;
for(i=0;i<g[u].size();i++)
{
Edge e=g[u][i];
v=e.to;
if(e.cap>0&&dis[v]<dis[u]+e.cost)
{
prev[v]=u;
pree[v]=i;
dis[v]=dis[u]+e.cost;
update=true;
}
}
}
}
if(dis[t]==-INF)
break;
for(u=t;u!=s;u=prev[u])
{
Edge &e=g[prev[u]][pree[u]];
e.cap-=1;
g[e.to][e.rev].cap+=1;
}
res+=dis[t];
f-=1;
}
return res;
}
int main()
{
int i,j,c,m,n,V,cost,num,u,v,s,t,k=0,x;
scanf("%d",&c);
while(c--)
{
num=0;
x=0;
scanf("%d%d",&m,&n);
V=m*n;
s=1;
t=V*2;
for(i=s;i<=t;i++)
g[i].clear();
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{
id[i][j]=++num;
scanf("%d",&cost);
if(num==1)
{
add_edge(num,num+V,2,cost);
x+=cost;
}
else if(num==V)
{
x+=cost;
add_edge(num,num+V,2,cost);
}
else
add_edge(num,num+V,1,cost);
}
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{
int a=i+1;
int b=j;
if(a>=1&&a<=m&&b>=1&&b<=n)
{
u=id[i][j];
v=id[a][b];
add_edge(u+V,v,1,0);
}
a=i;
b=j+1;
if(a>=1&&a<=m&&b>=1&&b<=n)
{
u=id[i][j];
v=id[a][b];
add_edge(u+V,v,1,0);
}
}
int res=min_cost_flow(s,t,2);
printf("Case %d: %d\n",++k,res-x);
}
return 0;
}
/*
2
3 3
1 1 1
1 0 1
1 1 1
3 4
1 1 0 1
1 1 1 1
0 1 10 1
*/