題目連接php
求一張無向帶權圖的邊雙連通生成子圖的最小代價。ios
核心的思路是,一個點雙連通份量確定是一堆環的並。c++
考慮增量地構造這個邊雙連通圖,每次把一個環並進去,至關於加入了一條鏈。git
那麼這個轉移須要:原集合的代價,鏈的代價,鏈的端點連入集合的代價。spa
設 \(A\) 爲新圖點集,\(S\) 爲原圖點集,設 \(f[S]\) 表示點集 \(S\) 構成邊雙連通份量的最小代價。code
設 \(T\) 爲新加入鏈的點集,\(u,v\) 分別爲加入的鏈的端點,設 \(g[u][v][T]\) 表示該鏈的最小代價。ip
設 \(mm[u][S]\) 表示點 \(u\) 向集合 \(S\) 中的點所連邊中,邊權最小值。
\[ f[A]=f[S]+g[u][v][T]+mn[u][S]+mn[v][S] \]
可是注意,若是新加入的鏈退化成了一個點,加入的代價就算少了。get
所以設 \(sec[u][S]\) 表示點 \(u\) 向集合 \(S\) 中的點所連邊中,邊權次小值。string
那麼對於 \(u=v\) 的狀況:
\[ f[A]=f[S]+g[u][u][T]+mn[u][S]+sec[u][S] \]
預處理 \(mn\) 和 \(sec\) 複雜度 \(\mathcal O(n^2\times 2^n)\)it
預處理 \(g\) 暴力枚舉一個端點的變化,複雜度 \(\mathcal O(n^3\times 2^n)\)
計算 \(f\) 須要枚舉子集,而後枚舉 \(u, v\) ,複雜度 \(\mathcal O(n^2\times 3^n )\)
#include <cmath> #include <cstdio> #include <cctype> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define N 15 #define M 105 #define S 4105 using namespace std; inline int rd() { int x = 0; char c = getchar(); while (!isdigit(c)) c = getchar(); while (isdigit(c)) { x = x * 10 + (c ^ 48); c = getchar(); } return x; } int n, m, tot, lim, hd[N]; struct edge{int w, to, nxt;} e[M << 1]; inline void add(int u, int v, int w) { e[++tot].to = v; e[tot].w = w; e[tot].nxt = hd[u]; hd[u] = tot; } //mn[i][S]: i 到 S 最短路 //sec[i][S]: i 到 S 次短路 //g[i][j][S]: 一條鏈,節點集合爲 S, 端點分別爲 i, j //f[S]: 集合爲 S 的合法方案 int f[S], g[N][N][S], mn[N][S], sec[N][S]; inline void mmin(int &x, int y) {x = min(x, y);} inline int countbit(int s) { int res = 0; for (int i = 0; i < n; ++i) res += ((s & (1 << i)) > 0); return res; } inline void work() { n = rd(); m = rd(); tot = 0; lim = (1 << n); for (int i = 0; i <= n; ++i) hd[i] = 0; for (int i = 1, u, v, w; i <= m; ++i) { u = rd() - 1; v = rd() - 1; w = rd(); add(u, v, w); add(v, u, w); } memset(f, 0x1f, sizeof(f)); memset(g, 0x1f, sizeof(g)); memset(mn, 0x1f, sizeof(mn)); memset(sec, 0x1f, sizeof(sec)); int inf = f[0]; //處理 mn 和 sec for (int s = 1; s < lim; ++s) for (int u = 0; u < n; ++u) if ((s & (1 << u)) == 0) for (int i = hd[u], v; i; i = e[i].nxt) { v = e[i].to; if ((s & (1 << v)) == 0) continue; if (e[i].w < mn[u][s]) { sec[u][s] = mn[u][s]; mn[u][s] = e[i].w; continue; } else sec[u][s] = min(sec[u][s], e[i].w); } //處理 g for (int u = 0; u < n; ++u) g[u][u][1 << u] = 0; for (int s = 1; s < lim; ++s) for (int u = 0; u < n; ++u) for (int x = 0; x < n; ++x) if (g[u][x][s] < inf) for (int i = hd[u], v; i; i = e[i].nxt) { v = e[i].to; if (s & (1 << v)) continue; mmin(g[v][x][s | (1 << v)], g[u][x][s] + e[i].w); } //處理 f for (int u = 0; u < n; ++u) f[1 << u] = 0; for (int nw = 1; nw < lim; ++nw) if (countbit(nw) >= 2) { for (int s = nw & (nw - 1); s; s = (s - 1) & nw) { int t = nw - s; for (int u = 0; u < n; ++u) if (s & (1 << u)) for (int v = 0; v < n; ++v) if (s & (1 << v) && g[u][v][s] < inf) { if (u == v) f[nw] = min(f[nw], f[t] + g[u][v][s] + mn[u][t] + sec[u][t]); else f[nw] = min(f[nw], f[t] + g[u][v][s] + mn[u][t] + mn[v][t]); } } } if (f[lim - 1] == inf) puts("impossible"); else printf("%d\n", f[lim - 1]); } int main() { int testcase = rd(); while (testcase--) work(); return 0; }