Poj 2112 [最大流] [二分圖的多重匹配]
阿新 • • 發佈:2019-01-10
此題涉及的知識點比較多:最短路徑,二分查詢,二分圖的多重匹配,最大流問題。
該題有3中解法:(都必須先二分答案,然後再用一下的方法)
1. 重新建圖,把多重匹配的點分裂成多個點來解二分圖的最大匹配
2. 直接解多重匹配(修改二分圖的最大匹配演算法中的一維陣列為二維陣列)
3. 轉化成最大流(新增起點s 和終點t ,重新建圖):s 到每個牛的邊權為1,每個擠奶器到 t 的邊權為 m.
三種方法我都寫了程式碼,提交的時間分別為:
1. 219MS 2. 79MS 3. 110MS
我覺得方法2和方法1的原理是一樣的,用一個二維陣列 match[][] 來標記就相當於把可多匹配的點分裂成了多個單匹配的點,但上面的時間說明方法2的效率比方法1高多了,仔細想想,方法2確實是能夠節約很多不必要的遍歷,這裡節約了時間。
至於方法3,我看 discuss 裡面都說用最大流來解此題效率非常低,時間達到 1000+ MS,難道是我用了 Improved - SAP 演算法???
方法1程式碼:
方法2(相比於方法1中修改的程式碼):#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> const int MAXK = 30 + 1; const int MAXC = 200 + 1; const int MAXM = 15 + 1; const int INF = 100000000; using namespace std; int k, c, m; int map[MAXK+MAXC][MAXK+MAXC]; bool path[MAXC][MAXK*MAXM]; int match[MAXK*MAXM]; bool vst[MAXK*MAXM]; /* 把每個擠奶器點分裂成 m 個點,選邊權 <=tmp 的邊建立二分圖 */ void buildGraph(int tmp) { memset(path, false, sizeof(path)); for (int i=1; i<=c; i++) for (int j=1; j<=k; j++) if (map[k+i][j] <= tmp) { for (int t=1; t<=m; t++) { path[i][(j-1)*m+t] = true; } } } bool DFS(int i) { for (int j=1; j<=k*m; j++) { if (path[i][j] && !vst[j]) { vst[j] = true; if (match[j] == -1 || DFS(match[j])) { match[j] = i; return true; } } } return false; } /* 針對該題,做了小小的修改,全部匹配返回 true, 否則返回 false */ bool maxMatch() { memset(match, -1, sizeof(match)); for (int i=1; i<=c; i++) { memset(vst, false, sizeof(vst)); if (!DFS(i)) return false; } return true; } /* 二分答案,求二分圖最大匹配 */ void solve() { int low = 1, high = 200*(k+c), mid; while (low < high) { mid = (low + high)/2; buildGraph(mid); maxMatch() == true ? high = mid : low = mid+1; } printf("%d\n", low); } void floyd() { int i, j, h, t = k+c; for (h=1; h<=t; h++) for (i=1; i<=t; i++) for (j=1; j<=t; j++) if (map[i][j] > map[i][h]+map[h][j]) map[i][j] = map[i][h]+map[h][j]; } int main() { scanf("%d %d %d", &k, &c, &m); for (int i=1; i<=k+c; i++) for (int j=1; j<=k+c; j++) { scanf("%d", &map[i][j]); if (map[i][j] == 0) map[i][j] = INF; } floyd(); solve(); }
方法3程式碼:bool path[MAXC][MAXK]; int match[MAXK][MAXM]; bool vst[MAXK]; /* 選邊權 <=tmp 的邊建立二分圖 */ void buildGraph(int tmp) { memset(path, false, sizeof(path)); for (int i=1; i<=c; i++) for (int j=1; j<=k; j++) if (map[k+i][j] <= tmp) { path[i][j] = true; } } /* 多重匹配要修改該函式,match[]改為二維陣列 */ bool DFS(int i) { for (int j=1; j<=k; j++) { if (path[i][j] && !vst[j]) { vst[j] = true; if (match[j][0] < m) { match[j][++match[j][0]] = i; return true; } for (int t=1; t<=m; t++) { if (DFS(match[j][t])) { match[j][t] = i; return true; } } } } return false; } /* 針對該題,做了小小的修改,全部匹配返回 true, 否則返回 false */ bool maxMatch() { memset(match, 0, sizeof(match)); for (int i=1; i<=c; i++) { memset(vst, false, sizeof(vst)); if (!DFS(i)) return false; } return true; }
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
const int MAXK = 30 + 1;
const int MAXC = 200 + 1;
const int MAXM = 15 + 1;
const int MAXV = 300 + 1;
const int MAXE = 20000 + 1;
const int INF = 100000000;
using namespace std;
struct Edge
{
int v, cap, flow;
Edge *next;
Edge *rev;
} edge[MAXE];
Edge *node[MAXV];
int nE;
int k, c, m;
int map[MAXK+MAXC][MAXK+MAXC];
inline void addEdge(int u, int v, int w)
{
edge[nE].v = v;
edge[nE].cap = w;
edge[nE].next = node[u];
node[u] = &edge[nE++];
}
inline void addRevEdge(int u, int v, int w)
{
addEdge(u, v, w);
addEdge(v, u, 0);
edge[nE-1].rev = &edge[nE-2];
edge[nE-2].rev = &edge[nE-1];
}
int n;
int start, tail; // 流網路的起點和終點
int dis[MAXV]; // 點i到終點的距離為 dis[i]
int num[MAXV]; // dis值為i的點的個數為 num[i]; 用於優化
/* 用 <=tmp 的邊建圖 */
void buildGraph(int tmp)
{
memset(node, 0, sizeof(node));
nE = 0;
for (int i=1; i<=k; i++)
for (int j=1; j<=c; j++)
{
if (map[k+j][i] <= tmp)
addRevEdge(k+j, i, 1); // 容量為1
}
start = k+c+1; // 新增一個起點
tail = k+c+2; // 新增一個終點
n = k+c+2;
for (int i=1; i<=c; i++)
addRevEdge(start, k+i, 1); // 起點到每個cow點的容量為1
for (int i=1; i<=k; i++)
addRevEdge(i, tail, m); // 每個k點到終點的容量為m
}
void BFS()
{
memset(dis, -1, sizeof(dis));
memset(num, 0, sizeof(num));
int Q[MAXV], p, q;
Q[0] = tail; p = 0; q = 1;
dis[tail] = 0;
num[0] = 1;
while(p < q)
{
int u = Q[p++];
for (Edge* e = node[u]; e; e = e->next)
{
if (e->rev->cap > 0 && dis[e->v] == -1)
{
dis[e->v] = dis[u] + 1;
num[dis[e->v]]++;
Q[q++] = e->v;
}
}
}
}
int maxFlow()
{
int u, totFlow = 0;
Edge *nextEg[MAXV], *preEg[MAXV];
for (int i=1; i<=n; i++)
nextEg[i] = node[i];
u = start;
while (dis[start] < n)
{
if (u == tail) // find an augmenting path
{
int augFlow = INF;
for (int i = start; i != tail; i = nextEg[i]->v)
if (augFlow > nextEg[i]->cap)
augFlow = nextEg[i]->cap;
for (int i = start; i != tail; i = nextEg[i]->v)
{
nextEg[i]->cap -= augFlow;
nextEg[i]->rev->cap += augFlow;
nextEg[i]->flow += augFlow;
nextEg[i]->rev->flow -= augFlow;
}
totFlow += augFlow;
u = start;
}
Edge *e;
for (e = nextEg[u]; e; e = e->next)
if (e->cap > 0 && dis[u] == dis[e->v]+1)
break;
if (e) // find an admissible arc, then Advance
{
nextEg[u] = e;
preEg[e->v] = e->rev;
u = e->v;
}
else // no admissible arc, then relabel this vertex
{
if (0 == (--num[dis[u]])) break; // GAP cut, Important!
nextEg[u] = node[u];
int mindis = n;
for (e = node[u]; e; e = e->next)
if (e->cap > 0 && mindis > dis[e->v])
mindis = dis[e->v];
dis[u] = mindis + 1;
++num[dis[u]];
if (u != start)
u = preEg[u]->v;
}
}
return totFlow;
}
/* 二分答案,最大流求解(I-SAP) */
void solve()
{
int low = 1, high = 200*(k+c), mid;
while (low < high)
{
mid = (low + high)/2;
buildGraph(mid);
BFS();
maxFlow() == c ? high = mid : low = mid+1;
}
printf("%d\n", low);
}
void floyd()
{
int i, j, h, t = k+c;
for (h=1; h<=t; h++)
for (i=1; i<=t; i++)
for (j=1; j<=t; j++)
if (map[i][j] > map[i][h]+map[h][j])
map[i][j] = map[i][h]+map[h][j];
}
int main()
{
scanf("%d %d %d", &k, &c, &m);
for (int i=1; i<=k+c; i++)
for (int j=1; j<=k+c; j++)
{
scanf("%d", &map[i][j]);
if (map[i][j] == 0)
map[i][j] = INF;
}
floyd();
solve();
}