此题的模型转化比较好
题目说是有向图,把图分成一些环,使得构成这些环总的边权值最小, 环的特性是最少两个点。
观察环这个限制,实际上就是每个点有且只有一个出边,有且只有一个入边,并且不能是自环
这可以跟匹配联系起来,将每个点拆成u, u' 然后 如果有一条边(u,v, w)就建一条(u, v ', w)的边
最后求匹配,如果左边的点都匹配到了,显然是每个点都有了一个出边,右边的点都匹配到后就是每个点都有了一个入边
这一点其实跟以前学计数那个循环有点相似。
#include <iostream> #include <algorithm> #include <cstring> #include <string> #include <cstdio> #include <cmath> #include <queue> #include <map> #include <set> #define eps 1e-5 #define MAXN 333 #define MAXM 333 #define INF 100000007 using namespace std; int n, m, ny, nx; int w[MAXN][MAXM]; int lx[MAXN], ly[MAXM]; int linky[MAXM]; int visx[MAXN], visy[MAXM]; int slack[MAXM]; bool find(int x) { visx[x] = 1; for(int y = 1; y <= ny; y++) { if(visy[y]) continue; int t = lx[x] + ly[y] - w[x][y]; if(t == 0) { visy[y] = 1; if(linky[y] == -1 || find(linky[y])) { linky[y] = x; return true; } } else if(slack[y] > t) slack[y] = t; } return false; } int KM() { memset(linky, -1, sizeof(linky)); for(int i = 1; i <= nx; i++) lx[i] = -INF; memset(ly, 0, sizeof(ly)); for(int i = 1; i <= nx; i++) for(int j = 1; j <= ny; j++) if(w[i][j] > lx[i]) lx[i] = w[i][j]; for(int x = 1; x <= nx; x++) { for(int i = 1; i <= ny; i++) slack[i] = INF; while(true) { memset(visx, 0, sizeof(visx)); memset(visy, 0, sizeof(visy)); if(find(x)) break; int d = INF; for(int i = 1; i <= ny; i++) if(!visy[i]) d = min(d, slack[i]); if(d == INF) return -1; for(int i = 1; i <= nx; i++) if(visx[i]) lx[i] -=d; for(int i = 1; i <= ny; i++) if(visy[i]) ly[i] += d; else slack[i] -= d; } } int tp = 0, cnt = 0; for(int i = 1; i <= ny; i++) if(linky[i] != -1 && w[linky[i]][i] != -INF) { tp += w[linky[i]][i]; cnt++; } if(cnt != nx) return -1; return -tp; } int main() { while(scanf("%d%d", &n, &m) != EOF) { int x, y, z; for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) w[i][j] = -INF; nx = ny = n; for(int i = 1; i <= m; i++) { scanf("%d%d%d", &x, &y, &z); if(-z > w[x][y]) w[x][y] = -z; } printf("%d\n", KM()); } return 0; }