1. 程式人生 > >LOJ 2318 「NOIP2017」寶藏

LOJ 2318 「NOIP2017」寶藏

題面

題目傳送門

解法

為什麼我的狀壓dp那麼醜啊……

  • 發現n12n≤12,所以不妨考慮狀壓dp
  • f[d][S][rt]f[d][S][rt]表示當前深度為dd,在根為rtrt的子樹中有SS集合內的點的最小代價
  • 考慮先列舉rtrt的一個兒子是什麼,假設為xx,然後列舉集合SSS'\subset S作為xx的子樹部分,其它剩下的部分繼續作為除xx以外的子樹部分
  • 可以發現這個dp很好轉移:f[d][S][rt]=min(d×v[rt][x]+f[d+1][S][x]+f[d][SS][rt])f[d][S][rt]=min(d×v[rt][x]+f[d+1][S'][x]+f[d][S-S'][rt])
  • 時間複雜度:O(3nn3)O(3^nn^3)
  • 為什麼是這個複雜度呢?因為列舉子集的整體複雜度為O(3n)O(3^n),這個可以使用二項式定理來證明。關於nn的狀態為O(n2)O(n^2),轉移的複雜度為O(n)O(n),所以總複雜度為O(3nn3)O(3^nn^3)

程式碼

#include <bits/stdc++.h>
#define inf 1ll << 40
#define ll long long
#define N 13 using namespace std; ll lg[1 << N], a[N][N], f[N][1 << N][N]; int lowbit(int x) {return x & -x;} ll dp(int d, int S, int rt) { if (!S) return 0; if (f[d][S][rt]) return f[d][S][rt]; ll ret = inf; for (int tx = S, ty = lowbit(S); tx; tx -= ty, ty = lowbit(
tx)) { int x = lg[ty], y = S ^ ty; ll tmp = a[rt][x] * d; for (int s = y; s; s = (s - 1) & y) ret = min(ret, tmp + dp(d + 1, s, x) + dp(d, y ^ s, rt)); ret = min(ret, tmp + dp(d, y, rt)); } return f[d][S][rt] = ret; } int main() { ios::sync_with_stdio(false); int n, m; cin >> n >> m; for (int i = 2; i < (1 << n); i++) lg[i] = lg[i >> 1] + 1; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) a[i][j] = inf; for (int i = 1; i <= m; i++) { int x, y; ll v; cin >> x >> y >> v; x--, y--; ll tx = min(a[x][y], v); a[x][y] = a[y][x] = tx; } ll ans = inf, mask = (1 << n) - 1; for (int i = 0; i < n; i++) ans = min(ans, dp(1, mask ^ (1 << i), i)); cout << ans << "\n"; return 0; }