POJ - 2175 Evacuation Plan (最小費用流消圈)
阿新 • • 發佈:2018-09-22
sca eof mat 反向 front 發的 lin i++ 意義
題意:有N棟樓,每棟樓有\(val_i\)個人要避難,現在有M個避難所,每個避難所的容量為\(cap_i\),每個人從樓i到避難所j的話費是兩者的曼哈頓距離.現在給出解決方案,問這個解決方案是否是花費最小的,若不是,則給出比這個更優的解.
分析:若只是要我們求一個最優解的話就用費用流做.現在要求判斷是否最優,那麽就是當前這張圖中是否最短路還能被更新.
首先需要根據給定的解決方案重現這個狀態下的殘余網,其實只需要加入必要的弧即可:對與任意的樓與避難所(i,j),建邊,費用為其距離;若i->j有流量,則反向弧也需要加入,費用為-|距離|.
對於源點s和匯點t.其實沒必要加入源點出發的邊,只考慮到達t的邊.這部分的弧顯然費用不用考慮,為0即可.因為匯點是與避難所相連,統計每個避難所的入流,若入流不為0,需要加入反向弧,若已經滿流,則說明已經不可增廣,則不用加入該弧.
最後從匯點出發跑一遍spfa,若存在負環,則只要在任意一個負環中走一遍即可減少費用.在spfa的過程中記錄每個點的前驅,這樣繞著環走一遍,註意判斷邊(i,j)的意義,可能是樓i到避難所j多去一個人,也可能是避難所j往i回退一個人.
#include<iostream> #include<cstring> #include<stdio.h> #include<algorithm> #include<set> #include<map> #include<vector> #include<stack> #include<queue> using namespace std; const int MAXN = 1005; const int MAXM = 100000; const int INF = 0x3f3f3f3f; struct Edge{ int to, next, cap, flow, cost; } edge[MAXM]; int head[MAXN], tot; int pre[MAXN], dis[MAXN]; bool vis[MAXN]; int cnt[MAXN]; int N; void init(int n) { N = n; tot = 0; memset(head, -1, sizeof(head)); } void AddEdge(int u, int v,int cost) { edge[tot] = (Edge){v,head[u],0,0,cost}; head[u] = tot++; //edge[tot] = (Edge){u,head[v],0,0,-cost}; //head[v] = tot++; } int spfa(int s){ queue<int> q; for (int i = 0; i < N; i++){ dis[i] = INF; vis[i] = false; pre[i] = -1; cnt[i] = 0; } dis[s] = 0; vis[s] = true; q.push(s); while (!q.empty()){ int u = q.front(); q.pop(); vis[u] = false; for (int i = head[u]; i != -1; i = edge[i].next){ int v = edge[i].to; if (dis[v] > dis[u] + edge[i].cost){ dis[v] = dis[u] + edge[i].cost; pre[v] = u; //記錄前驅 if (!vis[v]){ vis[v] = true; q.push(v); if(++cnt[v]>N) return v; //有負環,能減小花費 } } } } return -1; } struct Point{ int x,y,val; }p[MAXN],vz[MAXN]; int dist(const Point &a, const Point &b){ return abs(a.x-b.x) + abs(a.y-b.y) ; } int G[105][105]; int num[105]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int N,M; while(scanf("%d %d",&N, &M)==2){ init(N+M+2); int s=0,t=N+M+1; for(int i=1;i<=N;++i){ scanf("%d %d %d",&p[i].x, &p[i].y, &p[i].val); } for(int i=1;i<=M;++i){ scanf("%d %d %d",&vz[i].x,&vz[i].y, &vz[i].val); } memset(num,0,sizeof(num)); for(int i=1;i<=N;++i){ for(int j=1;j<=M;++j){ scanf("%d",&G[i][j]); int d = dist(p[i],vz[j]); AddEdge(i,j+N,d); num[j] += G[i][j]; if(G[i][j]) AddEdge(j+N,i,-d); //有流量說明有反向邊 } } for(int i=1;i<=M;++i){ //此處s表示實際網絡裏的匯點 if(num[i]){ //有流量則有反向邊 AddEdge(s,i+N,0); if(num[i]<vz[i].val){ //沒漫流說明還有繼續推進的空間 AddEdge(i+N,s,0); } } } int u = spfa(s); if(u==-1){ printf("OPTIMAL\n"); } else{ printf("SUBOPTIMAL\n"); memset(vis,0,sizeof(vis)); int v = u; while(!vis[pre[v]]){ vis[v] = 1; v = pre[v]; } int cur = v; do{ int fa = pre[cur]; if(fa<=N && cur>N) G[fa][cur-N]++; //走這條邊代表樓i->避難所j多一個人能更優 else if(fa>N && cur<=N) G[cur][fa-N]--; //同理 cur = fa; }while(cur!=v); for(int i=1;i<=N;++i){ for(int j=1;j<=M;++j){ printf("%d%c",G[i][j],j==M?‘\n‘:‘ ‘); } } } } return 0; }
POJ - 2175 Evacuation Plan (最小費用流消圈)