hdu3001压缩dp+3进制

入门的第一道压缩dp,书上的啥的写的全是片段,看了好久也没看懂,不过大致意思我明白了直接找了一道入门题接出来开朗多了,所以我要写一份很详细的解释,让初学者一定能看懂。
题目地址
我现在下来对压缩dp的理解就是,其实就是一种暴力解法,就像dfs一样,但是不一样的是,dfs所保存的参数是有限的,但是压缩dp就是拿一维出来保存所有的情况,需要对二进制的一些运算符熟练运用和理解。
这道题的思路也就是创建一个dp[s][j],表示在s状态下,以j节点为起点的最小代价。这是什么意思呢,我很久之前不知道在哪里看过这样一份解释我觉得是让我能直接上手写题的一个关键。他说比如我们在当前节点j,已经遍历过了一些点,我们把这些点放在一个集合s种,比如11001表示第1、4、5个点已经被遍历过了,比如我们现在要从j点到k点,那么dp[s + {k}][k] = min(dp[s + {k}][k], dp[s][j] + dist[j][k]);,而这道题的dp是这样一个意思,至少我是这样算的,我看也有人把s表示成还没遍历的点的集合,其实思路也是一样的。
好回归这题,这里说了一个节点可以走0、1、2次,但是二进制只能存0、1,不能保存走2次的情况,于是我们就用3进制,比如211表示节点1走了1次,节点2走了一次,节点3走了两次。
然后还有要注意的是,在二进制的压缩中,我们可以用<<进位符和&来直接表示状态s中某一个节点的值是不是1,比如s=10100,那么if (s&1<<2)就表示第3个节点的值是否是1,但是3进制就用不了2进制的<<了,所以我们要创建一个数组tri[s][j],表示状态s中第j位的值,比如14的三进制是112,那么tri[14][0] = 2,tri[1] = 1, tri[2] = 1,这样我们就能快速定位状态s的第j位的值是多少。
然后就是<<不能让我们快速得到3的n次方,所以我们还需要另外建立一个数组bit[i],其中bit[i]=3的i次方=1<<3(这里的<<等于用3进制重载后的符号,后面的思想用<<来想会更简单,所以下面的<<都表示bit[])。
接下来就是做题的思路了:
下面的节点都是从[0, n - 1]来算的。
首先我们要初始化状态,也就是起点,最简单的,点i到点i的值为0,那这种情况就是集合s中只有i这个点,且只走了一次,比如点3(下标是3,其实是第4个点)到点3的值为0,我们就表示为dist[1000][3] = 0,也就是dist[1<<3][3] = 0;
所以for (int i = 0; i < n; i++) dp[bit[i]][i] = 0;
下面开始枚举状态,由于我们的s的定义是已经遍历的节点,那么递推肯定是由少到多的,所以从0开始遍历,有n个节点,就遍历到1<<n个。for (int i = 0; i < bit[n]; i++)
状态枚举完了,我们就来枚举起点for (int j = 0; j < n; j++),如果当前状态中,节点j表示的值为0,就表示这种状态节点j一次都没走过,那就更别说通过j走到其他地方了,这时候我们就continue。if (!tri[i][j]) continue;
下面我们再枚举节点j能到哪些地方for (int k = 0; k < n; k++),如果j和k不相连,那么就到不了k,或者如果当前状态下节点k的值已经是2了,那就不能再走到k了,所以if (dist[k][j] == INF || tri[i][k] == 2) continue;。下面就要注意了,通过上面的筛选,对于那些合格的k,我们如何从当前状态s转移到k的值加了1的状态了,我们就可以这样写:i + bit[k],它就表示k的值加了1之后的状态,比如当前s的3进制状态是:20011,我们要对节点2(这里指的是下标2)的值加1,我们就加100,这样状态就变成了20111,也就是我们想要的状态,所以i+bit[k]就表示转移后的状态,所以状态转移方程就是:dp[i+bit[k]][k] = min(dp[i+bit[k]][k], dp[i][j] + dist[k][j]);
最后,我们要求答案的话,根据题意,我们就需要对那些遍历过所有点的状态求最小值,所以我们在枚举状态的时候就加入一个bool flag=true,表示是否遍历的所有点,在枚举节点j的时候,当tri[i][j] == 0的时候,我们就让flag=false,表示当前状态不符合答案。
最后我们可以发现在状态转移方程中,是完全向i递增递推的,所以对于当前状态一定是已经遍历好的了,这就更加验证了状态由小到大列举的正确性。
最后贴上ac代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 6e4;
const int INF = 1e9 + 7;
int dp[maxn][12], tri[maxn][12], bit[12];
int dist[12][12];
int n,m;
void init() {
    bit[0] = 1;
    for (int i = 1; i <= 10; i++) bit[i] = bit[i - 1] * 3;

    for (int i = 0; i < maxn; i++) {
        int tmp = i;
        for (int j = 0; j < 10; j++) tri[i][j] = tmp%3, tmp/=3;
    }
}

int solve() {
    for (int i = 0; i < n; i++) dp[bit[i]][i] = 0;

        int ans = INF;
        for (int i = 0; i < bit[n]; i++) {
            bool flag = true;
            for (int j = 0; j < n; j++) {
                if (!tri[i][j]) {
                    flag = false;
                    continue;
                }
                for (int k = 0; k < n; k++) {
                    if (dist[k][j] == INF || tri[i][k] == 2) continue;
                    dp[i+bit[k]][k] = min(dp[i+bit[k]][k], dp[i][j] + dist[k][j]);
                }
            }
            if (flag)
                for (int j = 0; j < n; j++)
                    ans = min(ans, dp[i][j]);
    }
    return ans==INF?-1:ans;
}


int main(){
    init();
    while(~scanf("%d %d", &n, &m)){
        for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) dist[i][j] = INF;
        for (int i = 0; i < bit[n]; i++) for (int j = 0; j < n; j++) dp[i][j] = INF;
        for (int i = 0; i < m; i++) {
            int a,b,c;
            scanf("%d %d %d", &a, &b, &c);
            if (dist[a - 1][b - 1] > c) dist[a - 1][b - 1] = dist[b - 1][a - 1] = c;
        }
        printf("%d\n", solve());
    }
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值