题意:
一个城市中有n个大楼,m个避难所,接下来给出n个大楼的坐标和人数,接下来m行给出每个避难所的坐标和容纳量,大楼到避难所的距离定义为曼哈顿距离+1,然后给出n行m列数据,第i行第j列代表从第i个大楼到第j个避难所去的人数,保证此方案合法,然后判断此方案是否所有人到避难所的距离和最小,是的话输出OPTIMAL,否则给出更好的方案,但不一定是最优的;
思路:
如果用最小费用最大流算法直接求解,会超时,此时有个更巧妙的算法,因为题目中已经给出了一套方案,然后就利用给出的方案建立残留网络图,寻找是否存在负圈,存在的话肯定不是最优,找到这个负圈,顺着负圈增广,得到一个更优(不一定最优)的解就行了,题目不要求最优。
所谓消圈定理,就是在某个流 中,如果其对应的残余网络(剩余流量为 0 的边视为不存在)没有负圈,那它一定就是当前流量下的最小费用流。反之亦然。即「 是最小费用流等价于其残余网络中没有负圈」。
建图是关键。建残量网络,每条边只需要一个剩余流量remains和代价cost即可,剩余流量为0意味着残量网络中不存在这条边。remains(i, j)表示 i 到 j 的残量,那么
比如上面这个边,u到v容量是11,流量是5,那么remain(u , v) = 11 - 5 = 6; 而remian(v , u) = 5(cap(v , u) = 0, flow(v , u) = -5)。对于正向弧(i,j),flow(i,j) = remain(j,i)。只要把remains更新好,最后输出flow时转成反向弧输出就好了。
建筑 i 到防空洞 j 的容量可设为INF,输入给出了流量,则remains(i , j) = INF - f, cost = …;
相应反向弧remains(j , i) = f,cost取反;
建立源点s, 设建筑 i 的流出的流量和为tot1[i], 而s 到 i 的容量为B[i].c(建筑i的人数),其残量为B[i].c - tot1[i].。
汇点同理。
邻接表
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 200+5;
int n, m;
int vis[maxn], dis[maxn], pre[maxn], ans[maxn][maxn];
struct Point{
int x,y,c;
}B[maxn], S[maxn];
// 图
struct Edge{
int u,v,flow,cost;
Edge(int a, int b, int c, int d):u(a),v(b),flow(c),cost(d){}
};
vector<Edge> edges;
vector<int> G[maxn];
void init(){
for(int i = 0; i < maxn; ++i) G[i].clear();
edges.clear();
}
void addEdge(int u, int v, int cost, int f1, int f2){
edges.push_back(Edge(u,v,f1,cost));
edges.push_back(Edge(v,u,f2,-cost));
int m = edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
int SPFA(int s, int t){
int num = n+m+2;
for(int i = 0; i < num; ++i) {
dis[i] = INF; vis[i] = 0;
}
queue<int> Q;
Q.push(t); Q.push(0);
dis[t] = 0;
while(!Q.empty()){
int x = Q.front(); Q.pop();
int cnt = Q.front(); Q.pop();
if(cnt >= num) return x;
vis[x] = 0;
for(int i = 0; i < G[x].size(); ++i){
Edge& e = edges[G[x][i]];
if(e.flow&&dis[e.v] > dis[x] + e.cost){
dis[e.v] = dis[x] + e.cost;
pre[e.v] = G[x][i];
if(!vis[e.v]) { Q.push(e.v); Q.push(cnt+1); vis[e.v] = 1; }
}
}
}
return -1;
}
void augument(int u){
memset(vis, 0, sizeof(vis));
while(!vis[u]){
vis[u] = 1;
u = edges[pre[u]].u;
}
int x = u;
do{
--edges[pre[u]].flow;
++edges[pre[u]^1].flow;
u = edges[pre[u]].u;
//printf("%d ",u);
}while(u != x);
}
void solve(int s, int t){
memset(ans, 0, sizeof(ans));
for(int i = 0; i < edges.size(); i+=2){
if(edges[i^1].flow == 0||edges[i].u == s||edges[i].v == t) continue;
ans[edges[i].u][edges[i].v-n] = edges[i^1].flow;
}
for(int i = 0; i < n; ++i){
for(int j = 0; j < m-1; ++j) printf("%d ", ans[i][j]);
printf("%d\n",ans[i][m-1]);
}
}
int main()
{
freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m) == 2&&n){
init();
for(int i = 0; i < n; ++i) scanf("%d%d%d",&B[i].x, &B[i].y, &B[i].c);
for(int i = 0; i < m; ++i) scanf("%d%d%d",&S[i].x, &S[i].y, &S[i].c);
// 读入plan
int tot1[maxn];// 记录每个建筑总的出流
int tot2[maxn]; // 记录每个防空洞总的入流
for(int i = 0; i < m; ++i) tot2[i] = 0;
for(int i = 0; i < n; ++i){
tot1[i] = 0;
for(int j = 0; j < m; ++j){
int f; scanf("%d",&f);
int cost = abs(B[i].x - S[j].x) + abs(B[i].y - S[j].y) + 1;
tot1[i] += f;
tot2[j] += f;
addEdge(i, j+n, cost, INF-f, f);
}
}
int s = n+m, t = n+m+1;
for(int i = 0; i < n; ++i) addEdge(s, i, 0, B[i].c-tot1[i], tot1[i]);
for(int i = 0; i < m; ++i) addEdge(i+n, t, 0, S[i].c-tot2[i], tot2[i]);
int u = SPFA(s,t);
if(u == -1) printf("OPTIMAL\n");
else{
printf("SUBOPTIMAL\n");
//printf("%d\n",u);
augument(u);
solve(s, t);
}
}
return 0;
}
邻接矩阵:
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define pii pair<int,int>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;
const int maxn = 200+5;
int n, m; // n: 建筑 m: 避难点
int remains[maxn][maxn], cost[maxn][maxn];
int pre[maxn], vis[maxn], dis[maxn];
struct Point{
int x,y,c;
}B[maxn], S[maxn];
int SPFA(int s, int t){
int num = n+m+2;
for(int i = 0; i < num; ++i) {
dis[i] = INF; vis[i] = 0;
}
dis[t] = 0;
queue<int> Q;
Q.push(t); Q.push(0);
while(!Q.empty()){
int x = Q.front(); Q.pop();
int cnt = Q.front(); Q.pop();
if(cnt >= num) return x;
vis[x] = 0;
for(int i = 0; i < n+m+2; ++i){
if(remains[x][i]&&dis[i] > dis[x] + cost[x][i]){
dis[i] = dis[x] + cost[x][i];
pre[i] = x;
if(!vis[i]){ Q.push(i); Q.push(cnt+1); vis[i] = 1;}
}
}
}
return -1;
}
void augument(int u){
memset(vis, 0, sizeof(vis));
while(!vis[u]){
vis[u] = 1;
u = pre[u];
}
int x = u;
do{
--remains[pre[u]][u];
++remains[u][pre[u]];
u = pre[u];
}while(u != x);
}
int main()
{
freopen("in.txt","r",stdin);
while(scanf("%d%d",&n,&m) == 2&&n){
memset(remains, 0, sizeof(remains));
for(int i = 0; i < n; ++i)scanf("%d%d%d",&B[i].x, &B[i].y, &B[i].c);
for(int i = 0; i < m; ++i)scanf("%d%d%d",&S[i].x, &S[i].y, &S[i].c);
// 读入plan
int tot1[maxn], tot2[maxn];
for(int i = 0; i < m; ++i) tot2[i] = 0;
for(int i = 0; i < n; ++i){
tot1[i] = 0;
for(int j = 0; j < m; ++j){
int f; scanf("%d",&f);
tot1[i]+= f;
tot2[j]+= f;
cost[i][j+n] = abs(B[i].x - S[j].x) + abs(B[i].y - S[j].y) + 1;
cost[j+n][i] = -cost[i][j+n];
remains[i][j+n] = INF-f;
remains[j+n][i] = f;
}
}
int s = n+m, t = n+m+1;
for(int i = 0; i < n; ++i){
remains[s][i] = B[i].c - tot1[i];
remains[i][s] = tot1[i];
}
for(int i = 0; i < m; ++i){
remains[i+n][t] = S[i].c - tot2[i];
remains[t][i+n] = tot2[i];
}
int u = SPFA(s,t);
if(u == -1) printf("OPTIMAL\n");
else{
printf("SUBOPTIMAL\n");
//printf("%d\n",u);
augument(u);
for(int i = 0; i < n; ++i){
for(int j = 0; j < m-1; ++j) printf("%d ",remains[j+n][i]);
printf("%d\n",remains[n+m-1][i]);
}
}
}
return 0;
}
参考:
https://blog.csdn.net/mypsq/article/details/39320887
https://blog.csdn.net/qq_34798152/article/details/53639562