题目大意:给一块n×m的地皮,上面有草地‘#’和洞‘.’ 。草地转换成洞需要d的费用,把洞填上种上草需要f的费用,在草地和洞之间加一道墙需要b的费用。现在问把这块n×m的地皮变成池子的最小费用是多少。(池子:边缘必须都是草地,每块草地与洞之间都有墙隔开)
解题思路:建图方式非常巧妙。先把所有边界上的洞全部变成草地,并记录费用sum。设置超级源点,连向所有边界的草地,容量为INF,然后连向其他草地,容量为d。设置超级汇点,使所有的洞,连向超级汇点,容量为f。然后地皮内每个点都与相邻的点连边,容量为b。
#####
##.##
#.#.#
#####
超级源点向(3,3)的#连了一条边,容量为d。如果将d转换为洞的话最终答案ans + 3b - (d + b)。所以要不要对(3,3)进行转换,就变成了3b和(d + b)也就是d 和2b谁大谁小的问题。当d大于2b时,把#转换成洞是不划算的,在最大流中体现为,当d >= 2b时,(3,3)点接受了(4,3)点流入的流,(3,3)点的流量就会化为三段b的流流向(2,3)(3,2)(3,4)三个点(最大流嘛,三条边满流),就相当于在不转化(3,3)点,并在其与(2,3)(3,2)(3,4)三个点间建墙,流量 = 费用 = 3b。当d < 2b时,即使接受了(4,3)点流入的流,(3,3)点的流量还是不够流满(2,3)(3,2)(3,4)三个点,所以最后流向三个点的流量并没有3b,而是本身流量加上(4,3)点流入的流量,流量 = 费用 = d + b,也就是把(3,3)点转换为洞啦。
题解转自:http://blog.csdn.net/llx523113241/article/details/48195599
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <string>
#include <map>
#include <set>
#include <queue>
#include <vector>
#include <stack>
#include <cctype>
using namespace std;
typedef unsigned long long ULL;
const int maxn = 3000;
const int INF = 0x3f3f3f3f;
bool used[maxn];
int n,m;///n行
struct edge{
int to,cap,rev;
};
vector <edge> G[maxn];
void addedge(int from,int to,int cap){
G[from].push_back((edge){to,cap,G[to].size()});
G[to].push_back((edge){from,0,G[from].size()-1});
}
int dfs(int v,int t,int f){
if(v==t) return f;
used[v] = 1;
for(int i =0;i<G[v].size();++i){
edge &e = G[v][i];
if(!used[e.to] &&e.cap>0){
int d = dfs(e.to,t,min(f,e.cap));
if(d>0){
e.cap -= d;
G[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}
int max_flow(int s,int t){
int flow = 0;
while(1){
memset( used,0,sizeof(used));
int f = dfs(s,t,INF);
if(f==0) break;
flow += f;
}
return flow;
}
int ans = 0,S,T;
int d,f,b;
int id[maxn][maxn];///节点编号
char g[maxn][maxn];///原图
int dx[4] = {0,0,1,-1};
int dy[4] = {-1,1,0,0};
void init(){
for(int i = 0;i<maxn;++i){
G[i].clear();
}
ans = 0;
}
///起点为0 终点为n*m+1
void changenum(){
S = 0;
T = m*n+1;
for(int i = 0;i < n;++i){
for(int j = 0;j < m;++j){
id[i][j] = i*m+j+1;
}
}
}
int main() {
int t;
scanf("%d",&t);
for(int tt = 1; tt<=t;++tt){
init();
scanf("%d%d%d%d%d",&m,&n,&d,&f,&b);///m列,变洞d,变草f,围栏b
for(int i = 0;i < n;++i){
scanf("%s",g[i]);
}
changenum();
for(int i = 0; i < n; ++i){
for(int j = 0; j < m; ++j){
if(j == 0 || j == m-1 || i == 0 || i == n-1){
if(g[i][j] == '.') ans += f;
addedge(S,id[i][j],INF);///起点向草连边
}
else{
if(g[i][j] == '.') addedge(id[i][j],T,f);///洞向终点连边
else addedge(S,id[i][j],d);///起点向草连边
}
///四个方向互相连边
for(int k = 0; k < 4; ++k){
int ni = i+dx[k],nj = j+dy[k];
if(ni<0 || ni>=n || nj<0 || nj>=m) continue;
addedge(id[i][j],id[ni][nj],b);
}
}
}
printf("%d\n",ans+max_flow(S,T));
}
return 0;
}
/*
3
3 3
5 5 1
#.#
#.#
###
5 4
1 8 1
#..##
##.##
#.#.#
#####
2 2
27 11 11
#.
.#
*/