SYSU-4, UVA 12711, 一般图最大(小)权匹配
题目大意:给你一个100个点的图,划分成两个点集,要求A点集所有点的度为奇数,B点集的点为偶数,求一个最小边权边集满足这个约束。
解:我们要先抽象提炼一些性质,才能得到这题一般图最小匹配的做法
首先一点,偶数点完全不用考虑,要么他们不选取,要么他们作为路径上的点即可(出度=入度),所以直接做一遍flyod,就可以把图变成只剩A点集的图了。
第二点,就是对于一个合法答案,一条边不可能被选取两遍或以上,因为这样可以可以去掉两次这条边,依然可以得到一个合法图,而且答案更小,这是用来证明上面抽象图的做法的正确性。
那么我们抽象了图以后,可以得知我们做一个最小匹配即可,因为这样能符合奇数点的约束,同时可以证明这样才是边权最小的选择(更大的图可以删边最终剩下匹配形式)
一般图的最小权匹配做法很多,另外一位大神的blog介绍了一种消圈的做法,但是我被xzj安利了一个随机算法去弄,感觉效果不错,代码也短而且好理解。
#include <cstdio> #include <string> #include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <complex> #include <set> #include <vector> #include <map> #include <queue> #include <deque> #include <ctime> using namespace std; const double EPS = 1e-8; #define ABS(x) ((x)<0?(-(x)):(x)) #define SQR(x) ((x)*(x)) #define MIN(a,b) ((a)<(b)?(a):(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) #define LSON(x) ((x)<<1) #define RSON(x) (((x)<<1)+1) #define LOWBIT(x) ((x)&(-(x))) #define MAXN 111 #define LL long long #define OO 214748364 int w[MAXN][MAXN], g[MAXN][MAXN]; int match[MAXN], path[MAXN], d[MAXN], p[MAXN], len; bool v[MAXN]; int n, m, k; void init() { scanf("%d%d%d", &n, &m, &k); for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) g[i][j] = OO; for (int i = 0; i < m; ++i) { int x, y, z; scanf("%d%d%d", &x, &y, &z); --x; --y; if (x == y) continue; if (z < g[x][y]) { g[x][y] = g[y][x] = z; } } for (int k = 0; k < n; ++k) for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) g[i][j] = min(g[i][j], g[i][k] + g[k][j]); for (int i = 0; i < k; ++i) { for (int j = 0; j < k; ++j) { w[i][j] = OO - g[i][j]; } } } bool dfs(int i) { path[len++] = i; if (v[i]) return true; v[i] = true; for (int j = 0; j < k; ++j) { if (i != j && match[i] != j && !v[j]) { int kok = match[j]; if (d[kok] < d[i] + w[i][j] - w[j][kok]) { d[kok] = d[i] + w[i][j] - w[j][kok]; if (dfs(kok)) return true; } } } --len; v[i] = false; return false; } void solve() { if (k&1) { puts("Impossible"); return ; } for (int i = 0; i < k; ++i) p[i] = i, match[i] = i ^ 1; int cnt = 0; for (;;) { len = 0; bool flag = false; memset(d, 0, sizeof(d)); memset(v, 0, sizeof(v)); for (int i = 0; i < k; ++i) { if (dfs(p[i])) { flag = true; int t = match[path[len - 1]], j = len - 2; while (path[j] != path[len - 1]) { match[t] = path[j]; swap(t, match[path[j]]); --j; } match[t] = path[j]; match[path[j]] = t; break; } } if (!flag) { if (++cnt >= 3) break; random_shuffle(p, p+k); } } int ans = 0; for (int i = 0; i < k; ++i) { int t = w[i][match[i]]; // cout << t << endl; if (t == 0) { puts("Impossible"); return ; } ans += OO - t; } printf("%d\n", ans / 2); } int main() { freopen("test.txt", "r", stdin); srand(time(0)); int cas; scanf("%d", &cas); for (int tt = 1; tt <= cas; ++tt) { printf("Case %d: ", tt); init(); solve(); } return 0; }