此题的模型转化比较好
题目说是有向图,把图分成一些环,使得构成这些环总的边权值最小, 环的特性是最少两个点。
观察环这个限制,实际上就是每个点有且只有一个出边,有且只有一个入边,并且不能是自环
这可以跟匹配联系起来,将每个点拆成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;
}