POJ-3686 The Windy's 犀利构图+KM
做这题的时候突然觉得每次修改一次可行标就一定能够找到增广路,因此把while(1)改成了int T = 2; while(T--) {}谁知道WA了,这就比较纠结了,后面手动模拟了一组数据才知道不在交错树的点不一定不在已经存在的匹配中,增加的可行边右端点可能就落在了一条匹配边上,那么这样一来并不能保证找到一条增广路。这样也就解释了为什么对于slack[]数组每次进行一个更新,因为这个值由于循环次数的增加可能多次被利用到,没有找到增广路,那么原来的那条交错树一定还会边遍历一遍,而且会有新的节点加入到S和T集合(其实反正都会遍历一遍,那么不更新slack数组,dfs过程中也会自动缩小)。
题意:有N个任务,M台机器去完成,每台机器完成每个任务有一个时间,现在问花在完成每个任务的平均时间最少为多少?每个任务所花时间为等待时间+完成时间。每台机器可以完成其中一项任务后改为完成另外一项任务,转换时间为0。
解法:题目要求计算所有任务完成的平均时间最小其实就是总完成时间最小,由样例分析得知每个任务所花费的时间是完成时间加上等待的时间,因此本题的要点就是如何处理多个任务在同一车间完成的匹配方案,同时还要处理等待时间。
设同一台机器上完成了K件工作,那么设这K件工作所花时间为t1, t2, t3..tK
那么总时间 T = t1+(t1+t2)+(t1+t2+t3)+...+(t1+t2+...tK)
拆分后就是t1*K+t2*(K-1)+t3*(K-2)+...+tK
因此可以将每台机器拆分成K个时间点,以每个任务在k时刻完成对总时间的贡献度连边,权值为ti*K。
如此构图后,求每个任务都进行匹配的一个完全匹配,且右边每个机器每一时刻只能够连一条边相当于同一时间只能处理一件任务。由于权值随着时间的推移递增,因此进行最小权值匹配保证一台机器不会出现中间某一时刻空闲的匹配方案。
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <iostream> using namespace std; const int INF = 0x3f3f3f3f; const int MaxN = 55*55; int N, M, LIM, w[55][MaxN]; int sx[55], sy[MaxN]; int lx[55], ly[MaxN]; int match[MaxN], slack[MaxN]; int path(int u) { sx[u] = 1; for (int i = 1; i <= LIM; ++i) { if (sy[i]) continue; int t = lx[u]+ly[i]-w[u][i]; if (t == 0) { // 如果该边属于等价子图 sy[i] = 1; if (!match[i] || path(match[i])) { match[i] = u; return 1; } } else { slack[i] = min(slack[i], t); // 更新slack数组 } } return 0; } int KM() { LIM = N*M; memset(ly, 0, sizeof (ly)); memset(lx, 0x80, sizeof (lx)); memset(match, 0, sizeof (match)); for (int i = 1; i <= N; ++i) { for (int j = 1; j <= LIM; ++j) { lx[i] = max(lx[i], w[i][j]); // 初始化可行标 } } for (int i = 1; i <= N; ++i) { // 为每一个工作都找到一个车间 memset(slack, 0x3f, sizeof (slack)); while (1) { memset(sx, 0, sizeof (sx)); memset(sy, 0, sizeof (sy)); if (path(i)) break; int d = INF; for (int j = 1; j <= LIM; ++j) { if (!sy[j]) d = min(d, slack[j]); } for (int j = 1; j <= N; ++j) { if (sx[j]) lx[j] -= d; } for (int j = 1; j <= LIM; ++j) { if (sy[j]) ly[j] += d; else slack[j] -= d; } } } int ret = 0; for (int i = 1; i <= LIM; ++i) { if (match[i]) { ret += w[match[i]][i]; } } return -ret; } int main() { int T; scanf("%d", &T); while (T--) { int x; scanf("%d %d", &N, &M); memset(w, 0, sizeof (w)); for (int i = 1; i <= N; ++i) { for (int j = 1; j <= M; ++j) { scanf("%d", &x); for (int k = 1; k <= N; ++k) { // 最极端的情况是所有工作都在一个车间完成 w[i][(j-1)*N+k] = -x*k; // 在M车间k时刻完成对总时间的贡献值 } } } printf("%f\n", 1.0*KM()/N); } return 0; }