UVa 1515 - Pool construction(最小割)
链接:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4261
题意:
输入一个h行w列的字符矩阵,草地用“#”表示,洞用“.”表示。
你可以把草改成洞,每格花费为d,也可以把洞填上草,每格花费为f。
最后还需要在草和洞之间修围栏,每条边的花费为b。
整个矩阵第一行/列和最后一行/列必须都是草。
求最小花费。2≤w,h≤50,1≤d, f, b≤10000。
分析:
围栏的作用是把草和洞隔开,让人联想到了“割”这个概念。
可是“割”只是把图中的结点分成了两个部分,而本题中,草和洞都能有多个连通块。怎么办呢?
添加源点S和汇点T,与其他点相连,则所有本不连通的草地/洞就能通过源点和汇点间接连起来了。
由于草和洞可以相互转换,而且转换还需要费用,所以需要一并在“割”中体现出来。
为此,规定与S连通的都是草,与T连通的都是洞,
则S需要往所有草格子连一条容量为d的边,表示必须把这条弧切断(割的容量增加d),
这个格子才能“叛逃”到T的“阵营”,成为洞。
由于题目说明了最外圈的草不能改成洞,从S到这些草格子的边容量应为正无穷
(在这之前需要把边界上的所有洞填成草,累加出这一步所需的费用)。
同理,所有不在边界上的洞格子往T连一条弧,费用为f,
表示必须把这条弧切断(割的容量增加f),才能让这个洞变成草。
相邻两个格子u和v之间需要连两条边u->v和v->u,容量均为b,
表示如果u是草,v是洞,则需要切断弧u->v;如果v是草,u是洞,则需要切断弧v->u。
这样,用最大流算法求出最小割,就可以得到本题的最小花费。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <queue> 4 #include <vector> 5 using namespace std; 6 7 /// 结点下标从0开始,注意maxn 8 struct Dinic { 9 static const int maxn = 50 * 50 + 5; 10 static const int INF = 0x3f3f3f3f; 11 struct Edge { 12 int from, to, cap, flow; 13 }; 14 15 int n, m, s, t; // 结点数,边数(包括反向弧),源点编号和汇点编号 16 vector<Edge> edges; // 边表。edges[e]和edges[e^1]互为反向弧 17 vector<int> G[maxn]; // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号 18 bool vis[maxn]; // BFS使用 19 int d[maxn]; // 从起点到i的距离 20 int cur[maxn]; // 当前弧下标 21 22 void init(int n) { 23 this->n = n; 24 edges.clear(); 25 for(int i = 0; i < n; i++) G[i].clear(); 26 } 27 void AddEdge(int from, int to, int cap) { 28 edges.push_back((Edge){from, to, cap, 0}); 29 edges.push_back((Edge){to, from, 0, 0}); 30 m = edges.size(); 31 G[from].push_back(m-2); 32 G[to].push_back(m-1); 33 } 34 bool BFS() { 35 memset(vis, 0, sizeof(vis)); 36 queue<int> Q; 37 Q.push(s); 38 vis[s] = 1; 39 d[s] = 0; 40 while(!Q.empty()) { 41 int x = Q.front(); Q.pop(); 42 for(int i = 0; i < G[x].size(); i++) { 43 Edge& e = edges[G[x][i]]; 44 if(!vis[e.to] && e.cap > e.flow) { // 只考虑残量网络中的弧 45 vis[e.to] = 1; 46 d[e.to] = d[x] + 1; 47 Q.push(e.to); 48 } 49 } 50 } 51 return vis[t]; 52 } 53 int DFS(int x, int a) { 54 if(x == t || a == 0) return a; 55 int flow = 0, f; 56 for(int& i = cur[x]; i < G[x].size(); i++) { // 从上次考虑的弧 57 Edge& e = edges[G[x][i]]; 58 if(d[x]+1 == d[e.to] && (f=DFS(e.to, min(a, e.cap-e.flow))) > 0) { 59 e.flow += f; 60 edges[G[x][i]^1].flow -= f; 61 flow += f; 62 a -= f; 63 if(a == 0) break; 64 } 65 } 66 return flow; 67 } 68 int Maxflow(int s, int t) { 69 this->s = s; this->t = t; 70 int flow = 0; 71 while(BFS()) { 72 memset(cur, 0, sizeof(cur)); 73 flow += DFS(s, INF); 74 } 75 return flow; 76 } 77 vector<int> Mincut() { // 在Maxflow之后调用 78 vector<int> ans; 79 for(int i = 0; i < edges.size(); i++) { 80 Edge& e = edges[i]; 81 if(vis[e.from] && !vis[e.to] && e.cap > 0) ans.push_back(i); 82 } 83 return ans; 84 } 85 } dc; 86 87 const int INF = 0x3f3f3f3f; 88 const int UP = 50 + 5; 89 int r, c; 90 char site[UP][UP]; 91 inline int id(int rr, int cc) { return rr*c+cc; } 92 93 int main() { 94 int T, d, f, b; 95 scanf("%d", &T); 96 while(T--) { 97 scanf("%d%d%d%d%d", &c, &r, &d, &f, &b); 98 for(int i = 0; i < r; i++) scanf("%s", site[i]); 99 int ans = 0; 100 for(int i = 0; i < r; i++) { 101 if(site[i][0] == '.') site[i][0] = '#', ans += f; 102 if(site[i][c-1] == '.') site[i][c-1] = '#', ans += f; 103 } 104 for(int i = 0; i < c; i++) { 105 if(site[0][i] == '.') site[0][i] = '#', ans += f; 106 if(site[r-1][i] == '.') site[r-1][i] = '#', ans += f; 107 } 108 109 dc.init(r*c+2); 110 int start = r*c, finish = r*c+1; 111 for(int rr = 0; rr < r; rr++) { 112 for(int cc = 0; cc < c; cc++) { 113 if(site[rr][cc] == '#') { 114 int cap = (rr==0 || rr==r-1 || cc==0 || cc==c-1) ? INF : d; 115 dc.AddEdge(start, id(rr,cc), cap); 116 } else dc.AddEdge(id(rr,cc), finish, f); 117 if(rr > 0) dc.AddEdge(id(rr,cc), id(rr-1,cc), b); 118 if(rr < r-1) dc.AddEdge(id(rr,cc), id(rr+1,cc), b); 119 if(cc > 0) dc.AddEdge(id(rr,cc), id(rr,cc-1), b); 120 if(cc < c-1) dc.AddEdge(id(rr,cc), id(rr,cc+1), b); 121 } 122 } 123 printf("%d\n", ans + dc.Maxflow(start, finish)); 124 } 125 return 0; 126 }