此题涉及的知识点比较多:最短路径,二分查找,二分图的多重匹配,最大流问题。
该题有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代码:
#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(); }方法2(相比于方法1中修改的代码):
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; }方法3代码:
#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(); }