POJ 2175 Evacuation Plan(网络流消负圈算法)

来源:互联网 发布:金融行业seo 编辑:程序博客网 时间:2024/05/22 03:17

这是挑战上的一道题。

一开始看到这道题,我是直接套最小费用流上去写的,然后无限T。。。

后来看到,原来不是要找最优的情况,只需要输出一组答案比当前结果花费小即可。

然后看书,才知道有消负圈算法这种东西。

费用流中最优的网络是不存在负圈的,最最简单的理解就是,

可以看到上面这个图,我们费用流是在最大流的前提下找最小费用,但是上面这个图肯定不是最大流,s到t还有1个流量单位。

何为负圈?我们在汇点t出发,然后跑最短路(有流量的路才可以通过,这里采用spfa),就会发现这个图会不断循环,先是dis[t] = 0, 然后dis[s] = -1, 然后dis[t] = -1,,然后dis[s] = -2,然后dis[t] = -2,此时节点t入队列次数大于2,所以有负圈。

有负圈说明什么呢?其实就是代表这个图有更优的结果,在负圈这条路上面,只要让正边流量-1,反边流量+1,就可以构成一个更优的结果。

可以把这个看成是一个定理。(反正我是只能理解到这个层面了- -)

所以,那就很好办了,只需要把题目所给的图给建立起来,然后从汇点跑一遍最短路。我没有用挑战上的floyd找负环,用了最习惯的spfa。

但是spfa有一个问题,就是他可以判跑着断跑着发现某个点入队列次数大于点数,就知道存在负圈,但是他不能保证这个点一定在这个圈里面。为什么呢?可以很容易想想来证明,你只要有负圈,这个圈上每个点都会把和他相连的点也更新掉,所以你不能保证这个点一定在这个圈里面,但是你可以保证这个点的前驱的前驱的前驱…………一定在这个圈里面。所以我们只需要记录一下每个点的前驱是谁。如果发现有负环的话,我们从发现的那个点不断找前驱,找到一个点出现了两次,那这个点肯定是在负圈里面的。

我知道哪个点在负圈里面了,此时我再不断找前驱,肯定都是在这个圈里面的。那我们还要更新一下流量,使得正边-1,反边+1(这个正反是在跑这个圈时候相对来说的)。所以我们还要记录一下每个点是由哪条边遍历过来的。

最后,输出答案即可。

代码如下:

#include<iostream>#include<cstdio>#include<vector>#include<queue>#include<utility>#include<stack>#include<algorithm>#include<cstring>#include<string>using namespace std;const int INF = 0x3f3f3f3f;const int maxn = 300 + 5;const int maxm = 100000 + 5;int n, m;int head[maxn], to[maxm], front[maxm], flow[maxm], cost[maxm], ppp;int dis[maxn], minflow[maxn];bool flag[maxn];int father[maxn], nx[maxn];int G[105][105];inline int read(){int x=0,t=1,c;while(!isdigit(c=getchar()))if(c=='-')t=-1;while(isdigit(c))x=x*10+c-'0',c=getchar();return x*t;}struct MIN_COST_MAX_FLOW {int spfa(int s, int e) {int u, v;int cnt[maxn] = {0};memset(flag, 0, sizeof(flag));fill(dis, dis + n + m + 10, INF);dis[s] = 0;queue <int> q;q.push(s);cnt[s]++;while(!q.empty()) {u = q.front();q.pop();flag[u] = 0;for(int i = head[u]; ~i; i = front[i]) {v = to[i];if(flow[i] && dis[v] > dis[u] + cost[i]) {dis[v] = dis[u] + cost[i];father[v] = u;nx[v] = i;if(!flag[v]) {cnt[v]++;flag[v] = 1;q.push(v);if(cnt[v] > n + m + 2)return v;}}}}return -1;}void solve(int s, int e){int p, root;root = spfa(s, e);if(root == -1) {cout << "OPTIMAL" << '\n';return;}cout << "SUBOPTIMAL" << '\n';memset(flag, 0, sizeof(flag));p = root;while(1) { //找到一个肯定在环里面的点 if(flag[p])break;flag[p] = 1;p = father[p];}root = p;memset(flag, 0, sizeof(flag));p = root;while(1) {//更新流量 if(flag[p])break;flag[p] = 1;int tmp = nx[p];flow[tmp] -= 1;flow[tmp ^ 1] += 1;p = father[p];}for(int i = 0, key = 0; i < n; i++) {//输出结果 for(int j = 0; j < m; j++) {int ans = INF - flow[key];if(ans < 0)ans = 0;printf("%d%c", ans, j == m - 1 ? '\n' : ' ');key+=2;}} }void add(int u, int v, int f, int c) {to[ppp] = v, front[ppp] = head[u], flow[ppp] = f, cost[ppp] = c, head[u] = ppp++;}}mcmf;int main() {#ifndef ONLINE_JUDGEfreopen("poj_in.txt", "r", stdin);#endifint bx[maxn], by[maxn], bb[maxn], ax[maxn], ay[maxn], ac[maxn];n=read(), m=read();memset(head, -1, sizeof(head));for(int i = 0; i < n; i++) {bx[i] = read(), by[i] = read(), bb[i] = read();}for(int i = 0; i < m; i++) {ax[i] = read(), ay[i] = read(), ac[i] = read();}for(int i = 0; i < n; i++) {for(int j = 0; j < m; j++) {G[i][j] = abs(bx[i] - ax[j]) + abs(by[i] - ay[j]) + 1;}}int s = n + m, t = s + 1;int sum[maxn] = {0};for(int i = 0, key = 0; i < n; i++) {for(int j = 0; j < m; j++) {int tmp = read();mcmf.add(i, n + j, INF - tmp, G[i][j]);mcmf.add(n + j, i, tmp, -G[i][j]);sum[j] += tmp;}}for(int i = 0; i < n; i++) {mcmf.add(s, i, 0, 0);mcmf.add(i, s, bb[i], 0);}for(int i = 0; i < m; i++) {mcmf.add(n + i, t, ac[i] - sum[i], 0);mcmf.add(t, n + i, sum[i], 0);}mcmf.solve(t, s);return 0;}