此题涉及的知识点比较多:最短路径,二分查找,二分图的多重匹配,最大流问题。
该题有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
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
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();
}