POJ-2175 Evacuation Plan 最小费用流、负环判定
题意:给定一个最小费用流的模型,根据给定的数据判定是否为最优解,如果不为最优解则给出一个比给定更优的解即可。不需要得出最优解。
解法:由给定的数据能够得出一个残图,且这个图满足了最大流的性质,判定一个残图是否满足最小费用最大流的判定依据类比于最大流中的层次图的构建,此时只需要判定图中是否存在负环即可。在写的过程中由于图中的编号和抽象了之后的编号有一个变换关系,所以处理要小心,一开始直接写了个最小费用最大流,通过费用来判定是否为最优TLE。找负环的时候,该题宜从汇点开始遍历,因为源点发出的边肯定全部满流(由于要疏散所有的人),而市政大楼与避难所在内部又分别没有边相连,所以要形成回路就必定会经过汇点。spfa跳出来的点也不一定就是环上的点,需要自己找出环上的一个点,方法就是沿着一条路进行标记,如果标记到了已经标记的点,那么这个点就一定在环上。
具体残图构图:
市政大楼(b)到避难所(s)两两之间一定有边,这在原图中也是不改变的,费用为两点之间的曼哈顿距离加1;
如果b到s已经有流量,那么s到b也一定有负费用的流量,费用为两点之间的曼哈顿距离加1;
如果源点到b有流量,那么b到源点一定有负费用的流量,如果从源点发出的边没有满流,那么仍然有边到达b,费用为0;
如果s到汇点有流量,那么汇点到s一定有负费用的流量,如果汇集到汇点的边没有满流,那么仍然有边到达汇点,费用为0;
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <queue> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; int N, M, SS, TT; int dis[205], vis[205], cnt[205]; int mp[205][205]; int way[205][205]; int path[205]; struct Point { int x, y, c; }b[105], s[105]; bool gao() { // 用来判定负圈的存在 int flag = false, pos; memset(dis, 0x3f, sizeof (dis)); memset(vis, 0, sizeof (vis)); memset(cnt, 0, sizeof (cnt)); queue<int>q; dis[TT] = 0, vis[TT] = 1, cnt[TT] = 1; q.push(TT); while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for (int v = 0; v <= TT; ++v) { if (mp[u][v] != INF && dis[v] > dis[u] + mp[u][v]) { dis[v] = dis[u] + mp[u][v]; path[v] = u; if (!vis[v]) { vis[v] = 1, ++cnt[v]; if (cnt[v] > TT) { flag = true; pos = v; break; } q.push(v); } } } } if (flag) { puts("SUBOPTIMAL"); memset(vis, 0, sizeof (vis)); int u = path[pos], v; while (1) { if (!vis[u]) { vis[u] = 1; u = path[u]; } else break; } int rec = u; v = u, u = path[u]; while (1) { if (u <= N) { way[u][v-N]++; } else { way[v][u-N]--; } if (u == rec) break; v = u, u = path[u]; } for (int i = 1; i <= N; ++i) { for (int j = 1; j <= M; ++j) { printf(j == 1 ? "%d" : " %d", way[i][j]); } puts(""); } } else { puts("OPTIMAL"); } } int dist(int m, int n) { return abs(b[m].x - s[n].x) + abs(b[m].y - s[n].y) + 1; } int num1[105], num2[105]; int main() { while (scanf("%d %d", &N, &M) != EOF) { SS = 0, TT = N+M+1; memset(num1, 0, sizeof (num1)); memset(num2, 0, sizeof (num2)); memset(mp, 0x3f, sizeof (mp)); // 若j>N, mp[i][j]表示从i到j-N的距离 for (int i = 1; i <= N; ++i) { scanf("%d %d %d", &b[i].x, &b[i].y, &b[i].c); } for (int i = 1; i <= M; ++i) { scanf("%d %d %d", &s[i].x, &s[i].y, &s[i].c); } for (int i = 1; i <= N; ++i) { for (int j = 1; j <= M; ++j) { scanf("%d", &way[i][j]); mp[i][N+j] = dist(i, j); num1[i] += way[i][j]; num2[j] += way[i][j]; if (way[i][j]) { mp[N+j][i] = -dist(i, j); } } } for (int i = 1; i <= N; ++i) { if (num1[i] < b[i].c) mp[SS][i] = 0; if (num1[i]) mp[i][SS] = 0; } for (int i = 1; i <= M; ++i) { if (num2[i] < s[i].c) mp[N+i][TT] = 0; if (num2[i]) mp[TT][N+i] = 0; } gao(); } return 0; }
TLE最小费用流代码,构边的方式是拆点,其实直接从源汇来控制流量也可。
/* POJ-2175 题意:N个点,每个点有一定人数,现在要从N个点到M个点进行人员派送,判定给出的方案是否合理 否则输出相比较而言较优的一种 分析:将N个点与M个之间进行构边,拆点限制N+M个点的容量,然后构图,最小费用流 */ #include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> #include <queue> using namespace std; int N, M, LIM; const int INF = 0x3f3f3f3f; int ini; int xi[205], yi[205], ci[205]; int cp[505][505]; // 用来存储容量 int ct[505][505]; // 用来存储费用 int mp[105][105]; int path[505]; int SS = 502, TT = 503; // 固定好源点和汇点 int dist(int a, int b) { return abs(xi[a] - xi[b]) + abs(yi[a] - yi[b]) + 1; } void build() { LIM = N+M; memset(cp, 0, sizeof (cp)); for (int i = 0; i < LIM; ++i) { // 拆点 cp[i<<1][i<<1|1] = ci[i]; ct[i<<1][i<<1|1] = 0, ct[i<<1|1][i<<1] = 0; } for (int i = 0; i < N; ++i) { // 从源点到市政大楼流量无穷大,费用为0,流量限制在拆点时确定 cp[SS][i<<1] = INF; ct[SS][i<<1] = 0, ct[i<<1][SS] = 0; } for (int i = N; i < LIM; ++i) { // 从避难所到汇点的流量无穷大,费用为0 cp[i<<1|1][TT] = INF; ct[i<<1|1][TT] = 0, ct[TT][i<<1|1] = 0; } for (int i = 0; i < N; ++i) { // 对从市政大楼到避难所构边 for (int j = N; j < LIM; ++j) { cp[i<<1|1][j<<1] = INF; ct[i<<1|1][j<<1] = dist(i, j); ct[j<<1][i<<1|1] = -dist(i, j); } } } bool spfa() { int dis[505]; char vis[505]; queue<int>q; memset(dis, 0x3f, sizeof (dis)); memset(vis, 0, sizeof (vis)); path[SS] = -1; dis[SS] = 0; vis[SS] = 1; q.push(SS); while (!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; // printf("u = %d\n", u); // getchar(); for (int v = 0; v <= TT; ++v) { if (cp[u][v] > 0 && dis[u] + ct[u][v] < dis[v]) { dis[v] = dis[u] + ct[u][v]; path[v] = u; // 记录最短路径 if (!vis[v] && v != TT) { vis[v] = 1; q.push(v); } } } } // printf("dis[TT] = %d\n", dis[TT]); return dis[TT] != INF; } void maxflow() { // 最小费用最大流的主要思想就是寻找最短路,然后根据这条最短路进行增广 LIM = N+M; int cost = 0; while (spfa()) { int u = path[TT], v = TT, f = INF; while (u != -1) { f = min(f, cp[u][v]); // printf("cp[][] = %d, f = %d\n", cp[u][v], f); v = u, u = path[u]; // printf("u = %d\n", u); // getchar(); } // printf("f = %d\n", f); u = path[TT], v = TT; while (u != -1) { cp[u][v] -= f, cp[v][u] += f; cost += f * ct[u][v]; v = u, u = path[u]; } } // printf("cost = %d, ini = %d\n", cost, ini); if (cost == ini) { puts("OPTIMAL"); } else { puts("SUBOPTIMAL"); for (int i = 0; i < N; ++i) { for (int j = N; j < LIM; ++j) { printf(j == N ? "%d" : " %d", cp[j<<1][i<<1|1]); } puts(""); } } } int main() { // freopen("1.in", "r", stdin); while (scanf("%d %d", &N, &M) != EOF) { ini = 0; for (int i = 0; i < N; ++i) { scanf("%d %d %d", &xi[i], &yi[i], &ci[i]); } for (int i = 0; i < M; ++i) { scanf("%d %d %d", &xi[N+i], &yi[N+i], &ci[N+i]); } // 读取每个点的左边和容量 for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { scanf("%d", &mp[i][j]); ini += mp[i][j] * dist(i, N+j); } } build(); maxflow(); } return 0; }