计蒜客 宝藏 状压dp (遍历二进制中一的子集的小技巧)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/du_lun/article/details/82414086

传送门

思路是参照着这个博客写的https://blog.csdn.net/a1035719430/article/details/80488083

dp的一个状态是点集,这个是很好想的,但是加上路径上宝藏的数量就不好想了,因为所有路径都挖完是一颗树,我们可以用一维表示状态中最大的深度,即dp[i][s]表示深度为i,包含s集合点的时候最小值

转移就是dp[i][t]=min(dp[i][t], dp[i-1][s]),表示从深度为i-1的s状态,转移到深度为i的t状态

另外我们需要处理出由s状态转移到t状态的最小花费

即 g[s][t],g[s][t]=t集合中不在s集合中的点,到s集合的最小值的和

一个点到一个集合的最小值就是这个点到集合中任意一个点花费的最小值

原创作者还有一个非常巧妙的技巧,设t状态,令s=t&(t-1)

每次令s=t&(s-1),就能用s去遍历t中所有一的子集,

如果想找s,中缺了t中的哪几个一,只需要令x=s^t即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=0x3f3f3f3f3f3f3f3f;
ll dp[13][5000], h[13][5000], g[5000][5000], cost[13][13];
/*
dp:深度为i,状态为s时的最小值
h : 将i加到点集s需要的最小花费,i不属于s
g :由s状态转移到t状态所需的最小化费
*/
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i=0; i<n; i++) for(int j=0; j<n; j++) cost[i][j]=INF;

    int u, v, w;
    for(int i=1; i<=m; i++){
        scanf("%d%d%d", &u, &v, &w);
        u--; v--;
        if(cost[u][v]>w) cost[u][v]=cost[v][u]=w;
    }
    int up=1<<n;
    for(int i=0; i<n; i++){
        for(int j=0; j<up; j++){
            h[i][j]=INF;
            if(!(j&(1<<i))){
                for(int k=0; k<n; k++){
                    if((1<<k)&j)
                        h[i][j]=min(h[i][j], cost[i][k]);
                }
            }
        }
    }

    for(int t=1; t<up; t++){
        int s=t&(t-1);
        //printf("t: %d\n", t);
        while(s){
            //printf("s: %d\n", s);
            int x=t^s;
            for(int i=0; i<n; i++){
                if(x&(1<<i)){
                    g[s][t]+=h[i][s];
                    if(g[s][t]>INF) g[s][t]=INF;
                }
            }
            s=t&(s-1);
        }
    }

    for(int i=0; i<n; i++) for(int j=0; j<up; j++) dp[i][j]=INF;
    for(int i=0; i<n; i++) dp[0][1<<i]=0;

    for(int i=1; i<n; i++){
        for(int t=1; t<up; t++){
            int s=t&(t-1);
            while(s){
                ll tmp;
                if(g[s][t]>=INF)
                    tmp=INF;
                else
                    tmp=1ll*i*g[s][t];
                dp[i][t]=min(dp[i][t], dp[i-1][s]+tmp);
                s=t&(s-1);
            }
        }
    }

    ll ans=INF;
    for(int i=0; i<n; i++)
        ans=min(dp[i][up-1], ans);
    printf("%lld\n", ans);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/du_lun/article/details/82414086