1. 程式人生 > >[NOIP2017]寶藏 子集DP

[NOIP2017]寶藏 子集DP

題面:[NOIP2017]寶藏

題面:

  首先我們觀察到,如果直接DP,因為每次轉移的代價受上一個狀態到底選了哪些邊的影響,因此無法直接轉移。

  所以我們考慮分層DP,即每次強制現在加入的點的距離為k(可能實際上小於k),這樣就可以忽略掉上個狀態選了哪些邊的影響了。

  所以這樣為什麼是正確的呢?

  設f[i][j]表示DP到第i層,狀態為j的最小代價。(即每層離起點最遠的點的距離為i - 1,所以下次轉移的點距離為i)

  那麼如果一個點被錯誤的計算了代價,當且僅當這個點離起點的距離小於i,但我們依然按照i的距離來計算了代價。

  那麼可以證明,這個點一定會在正確的層數被計算一次(i之前的某一層),那麼由於當前層數導致代價被多算,因此肯定沒那麼優,所以不會對答案造成影響。

  因此我們直接DP即可。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define R register int
 4 #define AC 15
 5 #define ac 12000
 6 #define inf 2139062143
 7 #define LL long long
 8 
 9 int n, m, maxn, ans = inf;
10 int f[AC][ac], in[AC], g[AC][AC], dis[ac][AC];
11 
12 inline int
read() 13 { 14 int x = 0;char c = getchar(); 15 while(c > '9' || c < '0') c = getchar(); 16 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 17 return x; 18 } 19 20 inline void upmin(int &a, int b){ 21 if(b < a) a = b; 22 } 23 24
void pre() 25 { 26 n = read(), m = read(), maxn = (1 << n) - 1; 27 memset(g, 127, sizeof(g)); 28 for(R i = 1; i <= m; i ++) 29 { 30 int a = read(), b = read(), c = read(); 31 upmin(g[a][b], c), upmin(g[b][a], c); 32 } 33 int tmp = 1; 34 for(R i = 1; i <= n; i ++) 35 in[i] = tmp, tmp <<= 1, g[i][i] = 0; 36 } 37 38 void get()//獲取所有聯通塊到各個點的距離,預處理可以降低複雜度 39 { 40 memset(dis, 127, sizeof(dis)); 41 for(R k = 1; k <= maxn; k ++) 42 { 43 for(R i = 1; i <= n; i ++)//列舉集合內的一點 44 { 45 if(!(k & in[i])) continue; 46 for(R j = 1; j <= n; j ++) 47 { 48 if(k & in[j]) dis[k][j] = 0; 49 else upmin(dis[k][j], g[i][j]); 50 } 51 } 52 } 53 } 54 55 void work() 56 { 57 memset(f, 127, sizeof(f)); 58 for(R k = 1; k <= n + 1; k ++)//列舉當前層(走下一步的最遠距離) 59 { 60 for(R i = 1; i <= n; i ++) f[k][in[i]] = 0; 61 upmin(ans, f[k][maxn]); 62 for(R i = 1; i <= maxn; i ++)//列舉狀態 63 { 64 if(f[k][i] == inf) continue;//不判斷這個可能會爆int 65 int s = i ^ maxn;//獲取補集 66 for(R j = s; j; j = (j - 1) & s)//列舉補集的子集 67 { 68 int tmp = 0;bool flag = true; 69 for(R l = 1; l <= n; l ++) 70 { 71 if(!(j & in[l])) continue;//如果不在這個子集中就跳過 72 if(dis[i][l] == inf) {flag = false; break;} 73 tmp += k * dis[i][l]; 74 } 75 if(flag) upmin(f[k + 1][i | j], f[k][i] + tmp); 76 } 77 } 78 } 79 printf("%d\n", ans); 80 } 81 82 int main() 83 { 84 // freopen("in.in", "r", stdin); 85 pre(); 86 get(); 87 work(); 88 // fclose(stdin); 89 return 0; 90 }
View Code