旅行商问题,即TSP问题(Traveling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。要求经过的路程为所有路径之中的最小值。
输入: 输出:22
5 8
0 1 3
0 3 4
1 2 5
2 0 4
2 3 5
3 4 3
4 0 7
4 1 6
如果采用搜索+剪枝,算法复杂度为O(n!)。
而这种情况下恰好可以考虑状态压缩,常用的状态压缩为二进制,对应0和1两种状态
(当然如果有三种状态的话就要使用3进制,哦不对我们应该用4进制,因为4进制比3进制更快一点)
状态压缩:将集合作为整数来记录状态的算法
基于状压的dp是一类以集合信息为状态且状态总数为指数级的特殊的动态规划问题
我们设置dp[S][u]表示已经访问的集合为S,当前所在的节点为u所对应的最优解
dp[S][u]=min{dp[S|{v}][v]+g[u][v] }(v不属于S)
因为dp[S][u]的下个状态就是dp[S|{v}][v]
下面给出记忆化dfs写法,一般来讲记忆化dfs比动态规划要快一点,因为动态规划往往会在计算无意义的状态中浪费时间。
- #include
//TSP旅行商问题 - using namespace std;
- int dp[1<<15][20],g[15][15],path[1<<15][15];//path为路径
- int n,m,INF;
-
- void init(){
- memset(dp,-1,sizeof(dp));
- memset(g,0x3f,sizeof(g));
- INF=g[0][0];
- }
-
- int run(int s,int u){
- if(dp[s][u]>=0) return dp[s][u];
- if(s==(1<
-1&&u==0) return dp[s][u]=0; - int ans=INF;
- for(int v=0;v
- if(!(s>>v&1)&&g[u][v]!=INF){
- int tmp=run(s|1<
- if(ans>tmp){
- ans=tmp;
- path[s][u]=v;//保存后继(如果不存储集合的话)
- }
- // ans=min(ans,run(s|1<
- }
- return dp[s][u]=ans;
- }
- void print(int s,int u){
- if(s==(1<
-1)return;//这是倒序输出的写法,先找到终态然后倒着输出。如果我们存的是前驱,那就要正序输出了 - int v=path[s][u];
- cout<<"->"<
- print(s|1<
- }
- int main(){
- int u,v,w;
- cin>>n>>m;
- init();
- for(int i=0;i
- cin>>u>>v>>w;
- g[u][v]=w;
- }
- run(0,0);
- cout<
0][0]<<'\n'; - cout<<0;
- print(0,0);//输出路径
- return 0;
- }
题目:吃馅饼POJ3311
题意:一个人从0开始送外卖,每次送外卖不超过10个地方,给你两两之间所需的时间,求送完外卖回到店里的总时间最小,每个地方可以经历任意次
输入:(0表示结束) 输出:8
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
0
思路:
因为每个点可以走很多次,那么最好就是先把两点间最短距离写出(floyd),然后就转化成了旅行商问题
因为我们要把所有点都走至少一次,那么当旅行商问题可以解出答案时,就一定是最佳答案
- #include
- using namespace std;
- const int M=19,INF=0x3f3f3f3f;
- int dp[1<<11][M],g[M][M],path[1<<15][15];//path为路径
- int n;
-
- void init(){
- memset(dp,-1,sizeof(dp));
- memset(g,0x3f,sizeof(g));
- }
-
- void floyd(){
- for(int k=0;k
- for(int i=0;i
- for(int j=0;j
- g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
- }
- int TSP(int s,int u){
- if(dp[s][u]>=0) return dp[s][u];
- if(s==(1<
-1&&u==0) return dp[s][u]=0; - int ans=INF;
- for(int v=0;v
- if(!(s>>v&1)&&g[u][v]!=INF){
- int tmp=TSP(s|1<
- if(ans>tmp){
- ans=tmp;
- path[s][u]=v;//保存后继(如果不存储集合的话)
- }
- //ans=min(ans,run(s|1<
- }
- return dp[s][u]=ans;
- }
- void print(int s,int u){
- if(s==(1<
-1)return;//这是倒序输出的写法,先找到终态然后倒着输出。如果我们存的是前驱,那就要正序输出了 - int v=path[s][u];
- cout<<"->"<
- print(s|1<
- }
- int main(){
- while(scanf("%d",&n)==1&&n){
- n++;//加原点
- init();
- for(int i=0;i
- for(int j=0;j
- scanf("%d",&g[i][j]);
- floyd();
- memset(dp,-1,sizeof(dp));
- printf("%d\n",TSP(0,0));
- cout<<0;
- print(0,0);//输出路径
- }
- return 0;
- }
-
相关阅读:
nginx 配置防盗链(了解)
抄写Linux源码(Day19:读取硬盘前的准备工作有哪些?)
计算机发展简史
计算机视觉项目实战-目标检测与识别
[N诺] 浙江大学历年机试题解
在RabbitMQ中 WorkQueue 工作队列 和发布(publish)/订阅(Subscribe) 有什么区别?
2024年6月20日 (周四) 叶子游戏新闻
LeGo-LOAM 跑通与源码学习
第3讲:MySQL数据库中常见的几种表字段数据类型
开源项目脚手架
-
原文地址:https://blog.csdn.net/m0_69478376/article/details/134516393