對於10^5個點10^6條邊的網路流,一般做法無法高效解決,如果所有邊都是雙向邊且網路能構成一個平面圖,則可以通過求解對偶圖的最短路求解,複雜度為O(M*log(M)),轉化方法類似於《平面圖S-T最小割》,詳見http://blog.csdn.net/kksleric/article/details/7863279
與S-T最小割平面圖較規則不同,痛點在於將一張圖的塊求出。大體分如下幾步進行:
①把所有的邊都拆成兩條有向邊,自環刪掉。
②將每條有向邊在另一個圖G‘中用一個點表示。
③考察原圖中的每個頂點,將所有的與之相連的邊極角排序。
④遍曆每條入邊。將其後繼設為與之順時針相鄰的出邊。也就是在G’中連一條從這個入邊的點到其後繼的有向邊。
###注意(S, T)的那條新加邊要特殊處理。
⑤在G'中就是一些不相交的有向環。每個有向環就對應一個地區。找出了所有的地區,我們要的那張圖就簡單了。
⑥根據對偶圖構圖,求得s-t之間最短路即是對應的最小割
###至於“死胡同”問題(構不成平面的邊)這樣會形成一個特殊的地區,相當於進去死胡同再出來。但是答案不會受到影響,所以直接忽略。
參見http://hi.baidu.com/sillycross/item/c12cc40bd0187a00addc70b1
hdu 4280 Island Transport代碼:
#pragma comment(linker, "/STACK:16777216")#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <algorithm>#include <vector>#include <set>using namespace std;const int maxn = 100010;const int maxm = 100010;const double inf = 1 << 28;struct node {int be, ne;double val;void init(int b, int e, double v) {be = b;ne = e;val = v;}};int cmp(double a, double b) {double eps = 1e-8;if (a - b > eps)return 1;else if (a - b >= -eps)return 0;elsereturn -1;}struct SPFA {node buf[maxm * 2];int len, E[maxn], queue[maxn];double d[maxn];void init() {memset(E, -1, sizeof(E));len = 0;}void add(int a, int b, double v) {if (a == b)return;buf[len].init(b, E[a], v);E[a] = len++;}int vis[maxn];double solve(int s, int t) {int head = 0, tail = 0;memset(vis, 0, sizeof(vis));memset(d, 255, sizeof(d));d[s] = 0;queue[(tail++) % maxn] = s;vis[s] = true;int a, b;while (head != tail) {a = queue[(head++) % maxn];vis[a] = 0;for (int i = E[a]; i != -1; i = buf[i].ne) {b = buf[i].be;if (cmp(d[a] + buf[i].val, d[b]) == -1) {d[b] = d[a] + buf[i].val;if (!vis[b]) {vis[b] = 1;queue[(tail++) % maxn] = b;}}}}return d[t];}} sp;struct arch {int in, out;double angle;arch(int a, int b, double c) {in = a;out = b;angle = c;}bool operator <(const arch& oth) const {return cmp(angle, oth.angle) == -1;}};int n, m;double px[maxn], py[maxn], cap[maxm];vector<arch> vertex[maxn];void init() {scanf("%d%d", &n, &m);double left = inf, right = -inf;int s = 0, t = 0;for (int i = 1; i <= n; i++) {scanf("%lf%lf", &px[i], &py[i]);vertex[i].clear();if (px[i] < left) {s = i;left = px[i];}if (px[i] > right) {right = px[i];t = i;}}int a, b;for (int i = 0; i < m; i++) {scanf("%d%d%lf", &a, &b, cap + i);if (a == b) {m--;i--;continue;}//①把所有的邊都拆成兩條有向邊,自環刪掉(有影響嗎?)。double agab = atan2(py[b] - py[a], px[b] - px[a]);double agba = atan2(py[a] - py[b], px[a] - px[b]);//②將每條有向邊在另一個圖G‘中用一個點表示。vertex[a].push_back(arch(i << 1, i << 1 | 1, agab));vertex[b].push_back(arch(i << 1 | 1, i << 1, agba));}a = m << 1, b = a | 1;//注意(S, T)的那條新加邊要特殊處理。vertex[s].push_back(arch(a, b, acos(-1.0)));vertex[t].push_back(arch(b, a, 0));}int nxt[maxm * 2], belong[maxm * 2], cnt;void find(int x, int root) {if (nxt[x] != root)find(nxt[x], root);belong[x] = cnt;}void build() {//③將所有的與之相連的邊極角排序。for (int i = 1; i <= n; i++) {sort(vertex[i].begin(), vertex[i].end());int ms = vertex[i].size();//④遍曆每條入邊。將其後繼設為與之順時針相鄰的出邊。也就是在G’中連一條從這個入邊的點到其後繼的有向邊。for (int j = 0; j < ms - 1; j++)nxt[vertex[i][j].in] = vertex[i][j + 1].out;nxt[vertex[i][ms - 1].in] = vertex[i][0].out;}int ms = m << 1 | 1;memset(belong, -1, sizeof(belong));cnt = 0;//⑤在G'中就是一些不相交的有向環。每個有向環就對應一個地區。找出了所有的地區,我們要的那張圖就簡單了。for (int i = 0; i <= ms; i++)if (belong[i] == -1 && ++cnt > 0)find(i, i);//構不成平面的邊,會形成一個特殊的地區,相當於進去死胡同再出來。但是答案不會受到影響,所以直接忽略。}double work() {int a, b;sp.init();//⑥根據對偶圖構圖,求得s-t之間最短路即是對應的最小割for (int i = 0; i < m; i++) {a = belong[i << 1];b = belong[i << 1 | 1];sp.add(a, b, cap[i]);sp.add(b, a, cap[i]);}a = belong[m << 1];b = belong[m << 1 | 1];return sp.solve(a, b);}int main() {int cas;scanf("%d", &cas);while (cas--) {init();build();printf("%.0f\n", work());}return 0;}