一。问题介绍

旅行商问题,即TSP问题(Travelling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求的路径路程为所有路径之中的最小值。

二。解决方法(理论)

TSP是一道经典的NP-完全问题。在规模比较小的时候,可以用动态规划求解。

有n个城市,两两之间均有道路直接相连,给出每个城市i和j之间的道路长度dist(i, j)。求一条经过每个城市一次且仅一次,最后回到起点的路线,使得经过的道路总长度最短。n<=16,城市编号为0~n-1。

因为路线是一个环,可以规定任意点为起点和终点,不妨设城市0为起点和终点。设d(s,i)表示“当前在i城市,已访问城市集合为s”的最短长度,则d(s, i)=min{d(s - i, j) + dist(j, i) | i∈S}.

初始时d({0},0)=0,最终答案为min{d({0,1,2,,,,n-1}, i) + dist(i, 0) | i≠0}

时间复杂度为O()。

根据该思路可以写下如下代码:

memset(dp, 0x3f, sizeof(dp));
dp[1][0] = 0;
for (int s = 0; s < (1<<n); s++) {    //枚举集合状态sfor (int i = 0; i < n; i++) {     //枚举当前的点iif (s & (1<<i)) {for (int j = 0; j < n; j++) {    //枚举之前的点jif (j != i && (s & (1<<j)) {dp[s][i] = min(dp[s][i], dp[s ^ (1<<i)][j] + dist[j][i]);    //根据之前的点j,更新当前的点i。}}}}
}
int ans = INF:
for (int i = 1; i < n; i++) {    //注意这里的i是从1开始的,因为访问城市的时候我们规定从0号城市访问,要走一个回路需要走n步,第n-1步必须是非0号城市,所以不能包括0。ans = min(ans, dp[(1<<n) - 1][i] + dist[i][0]);
}
cout << ans << endl;

【Note】

  • 上面的dp[s ^ (1<<j)][i]中的异或^表示集合s减去第i个元素。
  • 主要步骤是3步,西安枚举集合状态,再枚举当前点i,再枚举之前的点j。(前面的例题是正着推,前面的状态推后面所有的状态。本题的TSP是反着推,后面状态由所有前面的状态推出。)

三。实现代码步骤

1.先定义好用到的数组,定义一个dp[s][i]来表示“当前在城市i,已访问城市集合为s”的最短路径。在int dist[20][20];下面继续写下

int dp[1<<16][20];    //第一维表示已经走过的城市集合s, 第二维表示当前在哪个城市

2. 若考虑初始状态,起点是0,所以dp[1][0]=0,其他状态置为无穷大。

memset(dp, 0x3f, sizeof(dp));
dp[1][0] = 0;     //因为起点是0,所以当前访问的城市为0,第二维是0,将第0个城市加入到集合中,二进制表示为1,所以dp[1][0] = 0。

3. 从小到大遍历集合S,枚举当前所在的城市i,城市i必须在集合S中,即必须满足( s & (1<<i)) != 0.

【Note】枚举每个城市,也就是求dp[当前集合,第i个城市],求当前走到第i个城市的,且走过的城市集合相同的路径最小值。

for (int s = 0; s < (1<<n); s++) {for (int i = 0; i < n; i++) {if (s & (1<<i)) {}}
}

4. 枚举上一个城市j,j也必须在集合S中,所以必须满足(s & (1<<j) != 0.转移方程为d(s, i) = min{d{s-{i} + dist(j, i)  |  i ∈ S}。

for (int j = 0; j < n; j++) {if (j !=i && (s & (1<<j)) {    //第j个城市与第i个城市不相同,且第j个城市也在走过的集合S内。dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);}
}

【Note】这里巧妙地用异或^来表示集合的减法,如果集合B是集合A的子集,那么集合A异或集合B的结果就是A-B。如集合A为10100(二进制),集合B为100(二进制),则集合A^集合B = 10000(二进制),相当于集合A-集合B。当然dp[s ^ 1<<i][j] 也可以用dp[s-(1<<j)][j]表示,用异或主要是因为位运算比加减乘除运算快的多。

5. 最后我们枚举集合包含所有元素的状态,也即集合为{0, 1, 2.....n-1),并且返回起点0,然后找到最小值就是我们需要的答案,即mid(d({0, 1, 2,,,,n-1}, i) + dist(i, 0)  | i ≠ 0}

int ans = INF;
for (int i = 1; i < n; i++) {                        //注意,这里序号从1开始,因为我们假设回路是从0访问的,走到最后一个城市后,要返回第0个城市,所以不能包含第0个城市。ans = min(ans, dp[(1<<n) - 1][i] + dist[i][0];
}
if (ans == INF) ans = -1;    //如果找不到相应的路径,就返回-1
cout << ans << endl;

6.测试一下:

/* Input:
3
-1 1 10
1 -1 2
10 2 -1Output:
13*/

【Note】到了这里,程序基本就结束了,但是如果我们改一下TSP问题,去掉每个城市只能摆放一次的限制。这样一来,从城市i到城市j只需走最短路即可,那么我们要用Floyd算法求出每两个城市之间的最短路。则在main函数的memset(dp, 0x3f, sizeof(dp));前面写下:

for (int k = 0; k < n; k++) {for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);}}
}

四。整体代码;

1.未修改的TSP问题代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;int INF = 0x3f3f3f3f;
int dist[20][20];
int dp[1<<16][20];
int main() {int n;cin >> n;for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {cin >> dist[i][j];if (dist[i][j] == -1) dist[i][j] = INF;}}memset(dp, 0x3f, sizeof(dp));dp[1][0] = 0;for (int s = 0; s < (1<<n); s++) {for (int i = 0; i < n; i++) {if (s & (1<<i)) {for (int j = 0; j < n; j++) {if (j != i && (s & (1<<j))) {dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);}}}}}int ans = INF;for (int i = 1; i < n; i++) {ans = min(ans, dp[(1<<n) - 1][i] + dist[i][0]);}if (ans == INF) ans = -1;cout << ans << endl;return 0;
}

结果如下图所示。 结果为13.

2.修改条件后的TSP代码(也即取消一个城市只能拜访一次的条件)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;int INF = 0x3f3f3f3f;
int dist[20][20];
int dp[1<<16][20];
int main() {int n;cin >> n;for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {cin >> dist[i][j];if (dist[i][j] == -1) dist[i][j] = INF;}}memset(dp, 0x3f, sizeof(dp));//update Floyd Algorithmfor (int k = 0; k < n; k++) {for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);}}}dp[1][0] = 0;for (int s = 0; s < (1<<n); s++) {for (int i = 0; i < n; i++) {if (s & (1<<i)) {for (int j = 0; j < n; j++) {if (j != i && (s & (1<<j))) {dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);}}}}}int ans = INF;for (int i = 1; i < n; i++) {ans = min(ans, dp[(1<<n) - 1][i] + dist[i][0]);}if (ans == INF) ans = -1;cout << ans << endl;return 0;
}

结果如下,结果为6.

旅行商问题【状压dp】相关推荐

  1. 【动态规划】状压dp:蓝桥2020补给(旅行商问题)

    问题可以转换为: 从0出发途径每个城市至少一次返回0求最短路径: 解法:动态规划(状压dp)+Floyd预处理最短路径(从一个城市到另一个城市走最短路即可,不需考虑途径什么城市) [动态规划笔记]状压 ...

  2. HDU-3001(旅行商问题+三进制状压dp)

    #include <bits/stdc++.h> using namespace std; /* 题目大意: 给定n个点m条边的带权无向图,问有没有可能从n个点的其中一个出发,走遍其余n ...

  3. ACM-ICPC 2018 南京赛区网络预赛 E AC Challenge 状压DP

    题目链接: https://nanti.jisuanke.com/t/30994 Dlsj is competing in a contest with n (0 < n \le 20)n(0& ...

  4. 状压dp个人刷题记录

    目录 一.普通型 蒙德里安的梦想 题意: 思路: code: #2153. 「SCOI2005」互不侵犯 题意: 思路: code: P1879 [USACO06NOV]Corn Fields G 题 ...

  5. CSU1129 送货到家 状压dp

    哈哈发现这道题竟然没有题解,于是我决定写一份! 状压dp 题目: 懒惰的巫女Reimu因为各种原因在香霖堂的店主Rinnosuke那儿欠下了很多债,于是乎只好靠帮他在幻想乡中送货来偿还掉微不足道的一小 ...

  6. POJ 1038 Bugs Integrated Inc (复杂的状压DP)

    \(POJ~1038~~*Bugs~Integrated~Inc:\) (复杂的状压DP) \(solution:\) 很纠结的一道题目,写了大半天,就想练练手,结果这手生的.其实根据之前那道炮兵阵地 ...

  7. codeforces 8C. Looking for Order 状压dp

    题目链接 给n个物品的坐标, 和一个包裹的位置, 包裹不能移动. 每次最多可以拿两个物品, 然后将它们放到包里, 求将所有物品放到包里所需走的最小路程. 直接状压dp就好了. #include < ...

  8. UVA10296 Jogging Trails(中国邮递员问题)(欧拉回路、一般图最大权匹配 / 状压DP)

    整理的算法模板合集: ACM模板 目录 思路 UVA10296 Jogging Trails 题目翻译: 给你n个点,m条无向边,每条边有一定的距离数值,构造成一个连通图.问从任意一点出发,遍历所有的 ...

  9. POJ 2411 Mondriaan‘s Dream(最清楚好懂的状压DP讲解)(连通性状态压缩DP)

    poj 2411 Mondriaan's Dream(最清晰的状压DP解析) 闫氏DP大法好 我们这里是一列一列地来,因为是一个棋盘性的状态压缩DP,从哪个方向都一样 摆放的小方格总方案数 等价于 横 ...

  10. 【每日DP】day2、P1879 [USACO06NOV]Corn Fields G玉米地(状压DP模板题)难度⭐⭐⭐★

    昨天的每日DP我还在写01背包,今天就到状压DP了,真刺激. P1879 [USACO06NOV]Corn Fields G 题目链接 输入 2 3 1 1 1 0 1 0 输出 9 一道简单的状压D ...

最新文章

  1. 【6】font-size 字体属性
  2. sudo 命令表示 Linux sudo命令以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。 使用权限:在 /etc/sudoers 中有出现的使用
  3. 前端开发js运算符单竖杠“|”的用法和作用及js数据处理
  4. java 多线程 异步日志_精彩技巧(1)-- 异步打印日志的一点事
  5. Spring-JDBC通用Dao
  6. CF1146F - Leaf Partition(树形dp)
  7. graphql入门_GraphQL入门指南
  8. 前端学习(2680):注意看位置 少加注释
  9. error U1087: cannot have : and :: dependents for same target
  10. 每日英语:Lighting: Twigs Shine in Home Decor
  11. linux的系统移植——交叉编译工具集
  12. python box2d模拟平抛运动_论述如何基于Box2D模拟星球重力效果
  13. Java:URLEncoder、URLDecoder、Base64编码与解码
  14. mysql查询结果进行排名
  15. asp车辆租赁-汽车租赁管理系统
  16. 2018tfe世界计算机专业排名,2018年TFE TIMES美国研究生计算机科学专业排名
  17. 如何编写爬虫获取淘宝网上所有的商品分类以及关键属性 销售属性 非关键属性数据
  18. 目标检测一阶段和二阶段对比图
  19. reads去污染接头
  20. Deep Blind Video Super-resolution

热门文章

  1. java文件放在哪里_Java文件路径
  2. rmd中无法打开链结r_从零开始入门R语言—Rstudio下载与安装
  3. 【愚公系列】2021年12月 python爬虫自动化-爬虫环境搭建
  4. matlab mbd 淘宝,完美起航-基于模型(MBD)的树莓派程序开发——设置树莓派自动连接wifi和使用指令连接Matlab/Simulink和树莓派(不使用树莓派连接向导连接)...
  5. 北滘职业技术学校计算机,北窖职业技术学校
  6. Html网页按住Ctrl+滚轮缩放后,盒子大小改变的罪魁祸首竟是border?(已解决)
  7. MOS管工作原理,就是这么简单
  8. 华为无线-AC+AP小型无线网络配置实验_v1
  9. 小白机器学习基础算法学习必经之路
  10. 记 今日头条广告架构社招面试