狀態壓縮好題。下面咱們來一一分析一下這道題的整體想法。ios
容易發現,\(n\leq12\)暗示這道題時間複雜度爲指數級別的。c++
因爲題目代表測試
兩個已經被挖掘過的寶藏屋之間的道路無需再開發。優化
因此,咱們開發的路徑構成的只能是一棵樹,不能是有環的圖。spa
所以,咱們能夠將題目抽象成:給定一張有\(n\)個點的圖,求出一棵生成樹(根節點任意)使 \(\sum_{u\in G,fa_u\in G} dep_u\times w(u,fa_u)\)最小。code
因爲\(n\)很小,不難想到可使用狀態壓縮。ip
我最開始的想法是:枚舉根節點\(root\),並定義以當前的根爲根節點的狀態:\(d(i,j,S)\)表明當前葉子結點\(j\)距\(root\)距離爲\(i\)時,正處理的點集是\(S\)的最小值。開發
那麼\(i\geq2\)時有以下轉移:\(dp(i,j,S)=min(dp(i-1,k,S\setminus\{i\})+i\times d(i,k))\)。get
而\(j=1\)有:\(dp(i,j,S)=min(dp(x,k,S\setminus\{i\})+d(j,root))\)。string
初始化:讓\(dp(0,root,0)=0\),其他\(\infty\)。
這樣的作法效率大約是:\(O(n^42^{n})\),實際測試中全部的狀態是跑不滿的,故這種作法是能夠經過本題目的時間限制的。
咱們注意到剛剛那個轉移是至關混亂的,而且它會重複計算狀態值。
咱們上述作法本質上是利用葉子結點向上逆推推的過程。那麼,咱們是否能夠反過來想——從一個結點向它的子結點擴展,考慮剩下的子結點集合的代價。
根據此,咱們來從新定義以前的狀態表示:
\(dp(i,j,S)\)表明距離根節點距離\(i\)考慮到的當前結點爲\(j\),從\(j\)開始「挖」的點集爲\(S\)的最小代價和。換句話來說,咱們只考慮以\(j\)爲根的子樹的代價。
那麼有:\(dp(i,j,S)=min(dp(i+1,k,S'\setminus\{k\})+dp(i,j,S\setminus{S'})+d(j,k))\),這裏面的\(S'\)表明的是\(S\)中的子集,而k是S‘中的元素。
不難觀察到,方程相似於樹形DP中子樹合併這一重要思想。事實上,咱們利用它解決過樹形依賴關係的一類問題(揹包)。
初始化,咱們讓\(dp(i,j,0)=0\),其他正無窮,最終答案爲\(min(dp(0,i,U\setminus{i}))\)。時間複雜度爲\(O(n^32^n)\),具體計算過於複雜,在此不展開。
在實現的過程當中,爲優化常數,咱們能夠預處理出全部狀態下的元素個數、最小元素下標,以及將全部的點的下標平移。
另外,要注意邊界條件。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define RE register #define CLR(x, y) memset(x,y,sizeof x) #define FOR(i, x, y) for(RE int i=x;i<=y;++i) #define ROF(i, x, y) for(RE int i=x;i>=y;--i) using namespace std; const int N = 13, S = 1 << N, INF = 1e9 + 5; typedef long long LL; template <class T> void read(T &x) { bool mark = false; char ch = getchar(); for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') mark = true; for(x = 0; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0'; if(mark) x = -x; return; } int n, m, t, siz[S] = {}, tt[S] = {}; LL ans = INF, d[N][N], dp[N][N][S]; int main() { read(n), read(m); t = 1 << n, -- t; FOR(i, 0, n) { d[i][i] = 0; FOR(j, i + 1, n) d[i][j] = d[j][i] = INF; } LL u, v, w; FOR(i, 1, m) { read(u), read(v), read(w); -- u, -- v; d[u][v] = d[v][u] = min(d[u][v], w); } FOR(i, 1, t) siz[i] = siz[i & (i - 1)] + 1; for(int i = 0; i < n; ++ i) tt[1 << i] = i; FOR(i, 1, t) tt[i] = tt[i & (-i)]; CLR(dp, 0x3f); for(int i = 0; i < n; ++ i) for(int j = 0; j < n; ++ j) dp[i][j][0] = 0; for(int i = n - 2; i >= 0; -- i) { for(int j = 0; j < n; ++ j) { FOR(s, 1, t) { if((s & (1 << j)) || siz[t ^ s] <= i) continue; for(int S1 = s; S1; S1 = (S1 - 1) & s) { for(int tmp = S1, k = tt[S1]; tmp; k = tt[tmp ^= tmp & (-tmp)]) if(d[j][k] < INF) dp[i][j][s] = min(dp[i][j][s], dp[i + 1][k][S1 ^ (1 << k)] + dp[i][j][s ^ S1] + (i + 1) * d[j][k]); } } } } for(int i = 0; i < n; ++ i) ans = min(ans, dp[0][i][t ^ (1 << i)]); printf("%lld\n", ans); return 0; }
總結: