1. 程式人生 > >[NOIp2017] 寶藏

[NOIp2017] 寶藏

long 數量 print 進制 設立 sizeof continue fine include

類型:狀壓

傳送門:>Here<

題意:給出$N$個點藏有寶藏,有$M$條可以打通的邊(都未打通)。你可以選任意一個點作為起點出發,每一次可以打通一條邊來挖目的地的寶藏(不能打通已經挖完寶藏的兩個點之間的邊),其中打通一條邊的費用是$L \times K$,其中$L$表示邊的長度,$K$表示從當前點到起點打通的路徑經過了幾個點。問挖完所有寶藏的最小總費用

解題思路

由於$N \leq 12$,考慮狀壓。其中二進制狀態$S$中為$1$的那一位表示已經挖掘

可以枚舉起點$i$,設立數組$dp[S]$表示以$i$為起點且到達狀態$S$所需要的最小費用,因此答案就是$Min\{ dp[2^n-1] \}$

然後從起點開始搜索,每一次枚舉已經挖掘的所有點(設當前點為$j$,當前狀態為$S$),再枚舉這一次要打通的點$k$。記錄$dis[j]$表示從$j$到起點經過的點的數量,也就是題目中的$K$。則$$dp[S|k] = Min\{dp[S] + dis[j]*cost(j \rightarrow k)\}$$

這樣的復雜度並不是$N!$,因為$dp$數組是有條件更新的(有點類似記搜)

Code

註意需要判斷$i$到$j$有邊

/*By DennyQi 2018.8.17*/
#include <cstdio>
#include <queue>
#include 
<cstring> #include <algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; const int MAXN = 10010; const int MAXM = 27010; const int INF = 1061109567; inline int read(){
int x = 0; int w = 1; register int c = getchar(); while(c ^ - && (c < 0 || c > 9)) c = getchar(); if(c == -) w = -1, c = getchar(); while(c >= 0 && c <= 9) x = (x<<3) + (x<<1) + c - 0, c = getchar();return x * w; } int N,M,x,y,z,ans; int G[15][15],dp[8195],dis[15]; void DFS(int sta){ for(int i = 1; i <= N; ++i){ if(!(sta & (1<<(i-1)))) continue; for(int j = 1; j <= N; ++j){ if(sta & (1<<(j-1))) continue; if(G[i][j] == INF) continue; if(dis[i] * G[i][j] + dp[sta] < dp[sta | (1<<(j-1))]){ dp[sta | (1<<(j-1))] = dis[i] * G[i][j] + dp[sta]; dis[j] = dis[i] + 1; DFS(sta | (1<<(j-1))); } } } } int main(){ N=r,M=r; memset(G, 0x3f, sizeof G); ans = INF; for(int i = 1; i <= M; ++i){ x=r,y=r,z=r; if(z < G[x][y]){ G[x][y] = z; G[y][x] = z; } } for(int i = 1; i <= N; ++i){ memset(dp, 0x3f, sizeof dp); memset(dis, 0, sizeof dis); dis[i] = 1; dp[1<<(i-1)] = 0; DFS(1<<(i-1)); ans = Min(ans, dp[(1<<N) - 1]); } printf("%d", ans); return 0; }

[NOIp2017] 寶藏