NOIP2017 宝藏 状压DP

版权声明:本文为DyingShu原创文章,转载请注明出处哦。 https://blog.csdn.net/DyingShu/article/details/82707531

借鉴了一下这位dalao的题解
NOIP2017的D2T2,状压DP。
看到n的范围很容易想到是状压DP,但是怎么转移就很有趣了

d p [ i ] [ S ] dp[i][S] 表示最大深度为 i i 时,已开通的点状态为 S S 时的最小花费。
每次转移时,枚举不在集合中且可以转移的点,枚举子集进行转移。
d p [ i ] [ S ] dp[i][S] S S' 中所有点的更新代价更新 d p [ i + 1 ] [ S S ] dp[i +1][S | S']

为什么只枚举最大深度?因为由于枚举了所有子集,所以不会漏掉正解。(想象最优解所打通的那棵树,在状态转移的时候一定存在一个~~欧皇走位(大雾)~~的状态,每次更新都和最优解一模一样(最优解也是一层一层往下打通道路的),转移完毕之后就一定是最优解了,不存在漏解的情况。(好像说得不怎么清楚,不懂的可以留言问我哦

初始值: d p [ 0 ] [ 1 < < i ] = 0 dp[0][1<<i] = 0 (从地上开通的第一个结点)

有一些细节,注释标记在代码里了

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 13;
const int INF = 0x3f3f3f3f;

int dis[MAXN][MAXN];
int w[MAXN][1 << MAXN], dp[MAXN][1 << MAXN];
int Log[1 << MAXN], Nxt[MAXN], P[MAXN], g[1 << MAXN], Tmp[1 << MAXN];

int main(){
	freopen("in.txt", "r", stdin);
	memset(dis, 0x3f, sizeof(dis));
    int n, m; scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++){
        int a, b, l;
        scanf("%d%d%d", &a, &b, &l); a--, b--; //编号为0~n-1,方便状压 
        if(l < dis[a][b]) dis[a][b] = dis[b][a] = l;
    }
    
    for(int i = 0; i < n; i++) Log[1 << i] = i;
    memset(dp, 0x3f, sizeof(dp));
    for(int i = 0; i < n; i++) dp[0][1 << i] = 0;
    for(int i = 0; i < n; i++){ //深度 
        for(int x = 0; x < (1 << n); x++){ //状态 
            int tot = 0;
            for(int a = 0; a < n; a++){
                if(!(x & (1 << a))){ //该点还未被开通 
                    Nxt[tot] = 60000000; //这里不能设大了,否则会爆INT
                    //有两种解决方法:1.最大值小点 2.换成long long或者增加许多防止爆成负数的判断 
					P[tot] = 1 << a;
                    for(int j = x; j; j -= j&-j){ //提取所有1 
                        int b = Log[j&-j];
                        Nxt[tot] = min(1ll * Nxt[tot], 1ll * dis[a][b] * (i + 1));
                    }
                    tot++;
                }
            }
            for(int j = 1; j < (1 << tot); j++){
                g[j] = g[j - (j&-j)] + Nxt[Log[j&-j]]; //更新g[j]时,知道g[j - (j&-j)]已经被更新了
				//所以直接用g[j - (j&-j)]更新g[j]
                Tmp[j] = Tmp[j - (j&-j)] | P[Log[j&-j]];
                //此处的Tmp表示枚举集合为j时,实际在图中的集合
				//因为是预处理出所有不在集合中的点,相当于一个映射 
                dp[i + 1][x | Tmp[j]] = min(dp[i + 1][x | Tmp[j]], dp[i][x] + g[j]);
            }
        }
    }
    int ans = INF;
    for(int i = 0; i <= n; i++) ans = min(ans, dp[i][(1 << n) - 1]);
    printf("%d", ans);
    return 0;
}

以上。想了几天,主要是优化的细节太多了

猜你喜欢

转载自blog.csdn.net/DyingShu/article/details/82707531