NOIP2017 宝藏 状压DP

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

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

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

初始值: d p [ 0 ] [ 1 &lt; &lt; i ] = 0 dp[0][1&lt;&lt;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;
}

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值