海贼王之伟大航路

题目链接

思路:基本状压dp
看题目知道此题求的是最短哈密顿路径,也就是一条从1到n的经过所有点的最短路径。

我们可以使用状态压缩,使用一个二进制数state代表一种状态,state代表经过的所有点的集合。
例如
state=1,代表只经过1号点。
state=3(二进制为0011),代表经过1号点和2号点。
state=5(二进制为0101),代表经过1号点和3号点。
state=6(二进制为0110),代表经过2号点和3号点。

我们设定dp[state][j]代表经过点的集合为state,并且最后一个点为j号点。
由此推出状态转移方程为:dp[state][j] = min(dp[state - (1 <<j-1) ][k] + w[k][j] )
state - (1 <<j-1),表示从state点集中删去第j号点,w[k][j]代表从k到j路径的长度。
代表的含义为:依次遍历终点为j的每条边,以及对应的点集,从而可以推出dp[state][j]的最小值。
核心代码1:这是写的第一版代码,有个小问题

    //init
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= N; i++) {                                                      //只有一个点,所以路径为0
         dp[1<<(i-1)][i] = 0;
    }

    //
    for (int state = 1; state <= (1<<N) - 1; state++) {                     // 枚举所有状态

        for (int j = 1; j <= N; j++) {                                                 // 1 or 2

            if(state & 1 << (j-1) && state - (1 << (j-1))){                     //判断状态state是否包含第j个点,并且状态中最少有两个点。
                int minj = INT_MAX;
                for (int k = 1; k <= N; k++) {
                    minj = min(minj, dp[state-(1<<(j-1))][k]+G[k][j]);     //状态转移方程
                }
                dp[state][j] = minj;
            }
        }
    }

在上面的代码中的初始化部分,首先将所有状态初始化为INF,因为要求的是最小值,所有初始化为最大值,避免产生干扰。
然后将所有只有一个点的状态初始化为0.
接下来就是状态转移方程,求最小值。
但是,这里有问题,在遍历 j 的时候,我的 j 是从第一个点开始遍历的,这将导致求出来的最短路径可能不是从第一个点开始的,这样只能算出以最后一个点结尾的最短路径,而不能算出从第一个点开始,以最后一个点结尾的最短路径。
例如:

样例1:
0 10 20 999
5 0 90 30
99 50 0 10
999 1 2 0

最后求出的结果为:2 -> 1 -> 3 -> 4,明显看到不是以1号点开始。

核心代码2:改进后

   //init
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= N; i++) {                                                      //只有一个点,所以路径为0
         dp[1<<(i-1)][i] = 0;
    }

    //
    for (int state = 1; state <= (1<<N) - 1; state++) {                     // 枚举所有状态

        for (int j = 2; j <= N; j++) {                                                 // 1 or 2

            if(state & 1 << (j-1) && state - (1 << (j-1))){                     //判断状态state是否包含第j个点,并且状态中最少有两个点。
                int minj = INT_MAX;
                for (int k = 1; k <= N; k++) {
                    minj = min(minj, dp[state-(1<<(j-1))][k]+G[k][j]);     //状态转移方程
                }
                dp[state][j] = minj;
            }
        }
    }

改进之处为: j 不再从1号点开始,而从2号点开始,如此一来,相当于所有1号点的入边都被忽略了,只计算了1号点的出边。这样肯定只能从1号点开始。

最后附上AC代码:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <limits.h>
#include <iomanip>
#include <queue>
#include <cstring>
using namespace std;

typedef long long LL;

typedef vector<int> vec;

//#pragma GCC optimize(2)

static int N;

static int G[20][20];
static int dp[1<<20][20];                                         //dp[state][j]  经过的点集为state,最后一个点为j

void work(void){

    //init
    memset(dp, 0x3f, sizeof dp);

    for (int i = 1; i <= N; i++) {                                                     //只有一个点,所以路径为0
         dp[1<<(i-1)][i] = 0;
    }

    //
    for (int state = 1; state <= (1<<N) - 1; state++) {                       // 枚举所有状态

        for (int j = 2; j <= N; j++) {                                                      // 1 or 2

            if(state & 1 << (j-1) && state - (1 << (j-1))){                     //判断状态state是否包含第j个点
                int minj = INT_MAX;
                for (int k = 1; k <= N; k++) {
                    minj = min(minj, dp[state-(1<<(j-1))][k]+G[k][j]);
                }
                dp[state][j] = minj;
            }
        }
    }

    cout << dp[(1<<N)-1][N] << endl;
}


int main()
{

  //  freopen("E:\\Desktop\\data.txt", "r", stdin);
    //ios::sync_with_stdio(false);

    cin >> N;

    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            cin >> G[i][j];
        }
    }

    work();

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值