状压dp(经典例题)

最短Hamilton路径

这是一个经典的状压dp的题。

给定一张 𝑛 个点的带权无向图,点从 0∼𝑛−1 标号,求起点 0 到终点 𝑛−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 𝑛−1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数 𝑛。

接下来 𝑛 行每行 𝑛 个整数,其中第 𝑖 行第 𝑗 个整数表示点 𝑖 到 𝑗 的距离(记为 𝑎[𝑖,𝑗])。

对于任意的𝑥,𝑦,𝑧,数据保证𝑎[𝑥,𝑥]=0,𝑎[𝑥,𝑦]=𝑎[𝑦,𝑥] 并且 𝑎[𝑥,𝑦]+𝑎[𝑦,𝑧]≥𝑎[𝑥,𝑧]。

输出格式

输出一个整数,表示最短 Hamilton 路径的长度。

数据范围

1≤𝑛≤20
0≤𝑎[𝑖,𝑗]≤10^7

输入样例:

5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

输出样例:

18

代码如下:

//哪些点被用过
//目前停在哪个点上
//步骤
//确定dp数组的含义
//dp数组初始化
//dp数组如何遍历
//状态转移方程
//打印dp数组
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=20,M=1<<N;// <<表示左移,1向左移1位 -> 10

int dp[M][N],w[N][N];//w表示的是无权图
//dp[state][j],状态用二进制数表示,每个二进制位对应一个城市(或点)。
//当某个城市被访问过时,其对应的二进制位被设置为1,否则为0。
//dp[i][j]表示状态是i的情况下,到达点 j 的最短路径长度

int main()
{
    int n;
    cin>>n;

    for(int i=0;i<n;i++)
     for(int j=0;j<n;j++)
      cin>>w[i][j];

    memset(dp,0x3f,sizeof dp);//因为要求最小值,所以初始化为无穷大
    dp[1][0]=0;//因为零是起点,所以dp[1][0]=0;

    for(int i=0;i<1<<n;i++)//i表示所有的情况
     for(int j=0;j<n;j++)//j表示走到哪一个点
      if(i>>j&1)//判断状态i的第j位是否为1
       for(int k=0;k<n;k++)//k表示走到j这个点之前,以k为终点的最短距离
        if(i>>k&1)//判断状态i的第k位是否为1
                  //在二进制表示中,位的编号通常是从右往左数的,也就是从最低位开始数,从第0位开始
         dp[i][j]=min(dp[i][j],dp[i-(1<<j)][k]+w[k][j]);//更新最短距离

    cout<<dp[(1<<n)-1][n-1]<<endl;//表示所有点都走过了,且终点是n-1的最短距离
    //位运算的优先级低于'+'-'所以有必要的情况下要打括号
    //(1<<n)-1表示所有的城市都被访问过了。假设n=3,1<<3为1000,再减1的话为111,1表示被访问过。
    //这样操作以后我们就能得到全部都被访问过了,而且题目说的是0-n-1,所以(1<<n)-1表示0-n-1都被访问了
    return 0;
}

//状态转移方程
//dp[i-(1<<j)][k]:这表示在状态i中去掉了点j(即将状态i的第j位设置为0)后到达点k的最短路径长度。
//i-(1<<j)这会将状态i的第j位设置为0,而保持其他位不变。
//换句话说,它表示在还没有访问点j之前的某个时刻,到达点k的最短路径。
//w[k][j]:这表示从点k到点j的直接距离(或权重)。

变式:毕业旅行问题

小明目前在做一份毕业旅行的规划。

打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。

由于经费有限,小明希望能够通过合理的路线安排尽可能的省些路上的花销。

给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。

注意:北京为 1 号城市。

输入格式

第一行包含一个正整数 𝑛,表示城市个数。

接下来输入一个 𝑛 行 𝑛 列的矩阵,表示城市间的车票价钱。

输出格式

输出一个整数,表示最小车费花销。

数据范围

1<𝑛≤20,包括北京
车票价格均不超过 1000元。

输入样例:

4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0

输出样例:

13

说明

共 4 个城市,城市 1和城市 1的车费为 0,城市 1 和城市 2 之间的车费为 2,城市 1 和城市 3 之间的车费为 6,城市 1和城市 4 之间的车费为 5,以此类推。

假设任意两个城市之间均有单程票可买,且价格均在1000 元以内,无需考虑极端情况。

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 20, M = 1 << N, INF = 0x3f3f3f3f;

int n;
int w[N][N], f[M][N];

int main()
{
    cin>>n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            cin>>w[i][j];

    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;

    for (int i = 0; i < 1 << n; i++)//
        for (int j = 0; j < n; j ++ )
            if (i >> j & 1)//判断状态i的第j位是否为1
                for (int k = 0; k < n; k ++ )
                    if (i>>k&1)//判断状态i的第k位是否为1
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
    
    //计算从所有城市访问完其他城市后返回起始城市的最小总距离
    int res = INF;
    for (int i = 1; i < n; i ++ )
        res = min(res, f[(1 << n) - 1][i] + w[i][0]);
//f[][]里面包含着很多方案,有可能最后我从1->0,也用可能我从2->0,有很多种方案,但是我只要最小值
//每个城市之间均乘坐高铁,所以最后我直接返回起始城市(北京)就行了。
    cout<<res<<endl;

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值