NOIP2017寶藏
阿新 • • 發佈:2022-03-01
題意
給出 \(n\) 個點 \(m\) 條邊的圖,選一個點作為根,每選一個點的價值是 \(\text{dep} \times w\), 即深度(從0開始)乘邊權,求生成一棵樹的最小价值。
狀壓dp
狀態
考慮到深度不好壓縮,那就按照深度小到大往裡加點,即統一深度的點一起放進去,這樣深度就相同了。
設 \(f[dep][S]\) 表示現在深度是 \(dep\), 選了集合 \(S\) 中的點的最小价值。
列舉未加入的點,但是不知道它能接在哪些加入的點下面,怎麼辦?再多記一維狀態就會空間爆炸。
其實只要對每個選出的點連入已經加入的點就好,並且選最小的邊權。
因為如果深度不是當前的 \(dep\)
初始和最終狀態
\(f[0][2^i] = 0\) 表示選一個根。
其它都是 無窮大。
最後列舉深度, 就是最小的\(f[dep][U]\)。
轉移
\[f[dep][S] = \min_{T \cap S = \emptyset} \{ f[dep - 1][T] + \sum_{i \in T}w(i, S)\} \]其中 \(w(i, S)\) 表示點 \(i\) 連入 \(S\) 中的點的最小花費,預處理即可。
分析
預處理列舉子集和列舉連入的邊,時間是 \(O(n2^n)\)。
轉移列舉子集和補集再求和,總的時間複雜度是 \(O(n^23^n)\)
程式碼
#include<bits/stdc++.h> using namespace std; using ll = long long; const int MAXN = 15; const int INF = 0x3f3f3f3f; //const int mod = 1000000007; int mod; const double eps = 1e-9; template <typename T> void Read(T &x) { x = 0; T f = 1; char a = getchar(); for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f; for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48); x *= f; } inline int add(const int &a, const int &b) { static int c; c = a + b; if (c >= mod) c -= mod; if (c < 0) c += mod; return c; } inline int mul(const int &a, const int &b) { return 1ll * a * b % mod; } int qpow(int a, int b) { int sum(1); while(b) { if (b & 1) sum = mul(sum, a); a = mul(a, a); b >>= 1; } return sum; } int n, m; int val[MAXN][MAXN], dis[MAXN][ (1 << MAXN) + 10]; int f[MAXN][ (1 << MAXN) + 10]; int main() { memset(val, 0x3f, sizeof(val)); cin >> n >> m; for (int i = 1; i <= m; i ++) { int u, v, w; cin >> u >> v >> w; val[u][v] = val[v][u] = min(val[u][v], w); } memset(dis, 0x3f, sizeof(dis)); for (int i = 1; i <= n; i ++) for (int j = 1; j < (1 << n); j ++) for (int k = 0; k < n; k ++) if (j & (1 << k)) dis[i][j] = min(dis[i][j], val[i][k + 1]); memset(f, 0x3f, sizeof(f)); for (int i = 1; i <= n; i ++) f[0][1 << i - 1] = 0; int U = (1 << n) - 1; for (int i = 1; i < n; i ++) for (int j = 1; j < (1 << n); j ++) { int now = U ^ j; if (f[i - 1][now] == INF) continue; for (int k = j; k; k = j & (k - 1)) { int sum = 0; bool p = 0; for (int x = 0; x < n; x ++) if (k & (1 << x)) { if (dis[x + 1][now] == INF) { p = 1; break; } else sum += dis[x + 1][now] * i; } if (p) continue; f[i][now | k] = min(f[i][now | k], f[i - 1][now] + sum); } } int ans = INF; for (int i = 0; i < n; i ++) ans = min(ans, f[i][U]); cout << ans; return 0; }