題目大意:給你一個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; }
