这是挑战上的一道题。
一开始看到这道题,我是直接套最小费用流上去写的,然后无限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_JUDGE freopen("poj_in.txt", "r", stdin); #endif int 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; }