NOIP 2017 P3959 宝藏 (状态压缩DP板子)

洛谷题目传送门!!

题目的N这么小,当然是选择用状压DP啦!  等等,我好像不会状缩。。。。

首先,我们当然是要写状态转移方程了!!

那么,如果我们设  f[s]  状态s下,所要的最小花费,那么很显然有状态转移方程:

(s为总集合)

f[(1 << j) | s] =  f[s] + num[i] * dis[i][j]  (从 i 点走向 j点时)

其中,num表示之前已经走过的点的数量,dis表示从i到j的边的距离(这不就是题目要求吗)

很显然,这道题用DFS来做比较方便(跑图和回溯)

等等。

如果我们用不同的走法更新到同一个点,那么是否会影响到我们的更新呢? 显然是会的。

就比如,我们   1 -› 2 -› 3 -› 4  和 1 -›  3  -› 5 -› 2 - › 4  ,两种走法下,num的不同显然会给我们的更新带来不同的影响。

所以,上面的DP走法是有后效性的。

于是,我们要更新DP方程:

f[1 << j | s] = min( best + num[i] * dis[i][j]) , 其中 best 为上一层的最优解。

注意,起点不确定,怎么办?   当然是枚举起点啦!

#include <bits/stdc++.h>
using namespace std;
#define N 13
#define isdigit(c) ((c)>='0'&&(c)<='9')
#define min(a,b) ((a)<(b)?(a):(b))

inline int read(){
    int x = 0, s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-')s = -1;
        c = getchar();
    }
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s;
}

int f[N][1 << N][N];
int num[N], dis[N][N];
int n, m, limit;
int ans = (int)2e9;

/*
状态转移方程:  
    对于当前的这个点j,如果他处在第 deth 层, 从第i号点转移到 j 则有
    f[j][1 << j | s][deth] = best + num[i] * dis[i][j] 
    best 表示上一层最优解,dis表示i和j之间的距离 
*/

void dfs(int s, int best, int deth){
    if(best >= ans) return ;   /*剪枝*/
    if(s == limit) {
        ans = min(ans, best);   /*已经走完所有点*/
        return ;
    }
    for(int i = 0;i < n; i++){
        if(!(1 << i & s)) continue;   /*这个点还未走过,不能转移*/
        for(int j = 0;j < n; j++){
            if(!(1 << j & s) && dis[i][j] < (int)2e9){
                if(f[j][1 << j | s][deth + 1] > best + num[i] * dis[i][j]){
                    f[j][1 << j | s][deth + 1] = best + num[i] * dis[i][j];  /*进行转移*/
                    num[j] = num[i] + 1;  /*deth + 1, 因为j在i的下一层*/
                    dfs(1 << j | s, f[j][1 << j | s][deth + 1], deth + 1);
                }    
            }
        }
    }
    return ;
}

int main(){
    memset(dis, 127, sizeof(dis));
    n = read(), m = read();
    limit = (1 << n) - 1;
    for(int i = 1;i <= m; i++){
        int x = read() - 1, y = read() - 1, w = read();  /*编号减一, 给状压用(2 ^ 0 才是 1)*/
        dis[x][y] = dis[y][x] = min(dis[x][y], w);
    }
    for(int i = 0;i < n; i++){
        memset(num, 0, sizeof(num));
        memset(f, 127, sizeof(f));
        num[i] = 1;
        dfs(1 << i, 0, 0);  /*1 << i  把第i位设为 1*/
    }
    printf("%d
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/wondering-world/p/12987392.html