Luogu 3959 [NOIP2017] 宝藏

NOIP2017最后一道题

挺难想的状压dp。

受到深度的条件限制,所以一般的状态设计带有后效性,这时候考虑把深度作为一维,这样子可以保证所有状态不重复计算一遍。

神仙预处理:先处理出一个点连到一个集合所需要的最小代价,然后再处理出一个集合连到一个集合所需要的最小代价

设$g_{s, t}$表示从s集合连到t集合的最小代价, $f_{i, j}$表示当前深度为i,挖到集合s的最小代价,有转移:

    $f_{i, s} = min(g_{s, t} * (i - 1) + f_{i - 1, t})$  t是s的子集

最后的答案  $ans = min(f_{i, maxS})$ $(0<i<n)$

可以发现这样子最优答案一定会被计算到。

时间复杂度$O(3^{n} * 2 ^ {n} * n)$.

Code:

#include <cstdio>
#include <cstring>
using namespace std;

const int N = 15;
const int S = (1 << 12) + 5;
const int inf = 0x3f3f3f3f;

int n, m, e[N][N], h[N][S], g[S][S], f[N][S];

inline void read(int &X) {
    X = 0;
    char ch = 0;
    int op = 1;
    for(; ch > '9' || ch < '0'; ch = getchar())
        if(ch == '-') op = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

inline int min(int x, int y) {
    return x > y ? y : x;
}

inline void chkMin(int &x, int y) {
    if(y < x) x = y;
}

int main() {
    read(n), read(m);
    memset(e, 0x3f, sizeof(e));
    for(int x, y, v, i = 1; i <= m; i++) {
        read(x), read(y), read(v);
        e[x][y] = min(e[x][y], v);
        e[y][x] = min(e[y][x], v);
    }
    
/*    for(int i = 1; i <= n; i++, printf("\n"))
        for(int j = 1; j <= n; j++)
            printf("%d ", e[i][j]);   */
    
    for(int i = 1; i <= n; i++) {
        for(int s = 1; s < (1 << n); s++) {
            h[i][s] = inf;
            if(!(s & (1 << (i - 1))))
                for(int j = 1; j <= n; j++)
                    if(s & (1 << (j - 1)))
                        chkMin(h[i][s], e[i][j]);
        }
    }
    
    for(int s = 1; s < (1 << n); s++) {
        for(int t = s & (s - 1); t; t = s & (t - 1)) {
            int x = s ^ t;
            for(int i = 1; i <= n; i++) 
                if(x & (1 << (i - 1)))
                    g[s][t] = min(g[s][t] + h[i][t], inf);
        }
    }
    
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i <= n; i++)    f[1][1 << (i - 1)] = 0;
    for(int i = 2; i <= n; i++) {
        for(int s = 1; s < (1 << n); s++) {
            for(int t = s & (s - 1); t; t = s & (t - 1)) {
                int tmp;
                if(g[s][t] != inf) tmp = g[s][t] * (i - 1);
                else tmp = inf;
                if(f[i - 1][t] != inf) 
                    chkMin(f[i][s], f[i - 1][t] + tmp);
            }
        }
    }
    
    int ans = inf;
    for(int i = 1; i <= n; i++)
        chkMin(ans, f[i][(1 << n) - 1]);
    
    printf("%d\n", ans);
    return 0;
}
View Code

 

posted @ 2018-08-15 08:59  CzxingcHen  阅读(114)  评论(0编辑  收藏  举报