TSP问题 (tsp.cpp/c/pas) Time Limit : 1 sec , Memory Limit : 128MB Description 对于给定的加权有向图G(V,E),查找满足以下条件的最短路径的距离: 这条路径是一个环,即这条路径的起点和终点都是同一个点。 每个顶点只能访问一次。 Input |V| |E| s0 t0 d0 s1 t1 d1 : s|E|−1 t|E|−1 d|E|−1 |V| 是顶点数,|E|是边数。顶点以数字 0, 1,…, |V|-1表示。 si 和ti 表示第i条边(有向)的源和目标顶点,di 表示si和 ti (第i条边)之间的距离。 Output 在一行中输出最短距离。如果没有解决方案,则输出 -1。 Constraints 2 ≤ |V| ≤ 15 0 ≤ di ≤ 1,000 没有多重边
Sample Input 1 4 6 0 1 2 1 2 3 1 3 9 2 0 1 2 3 6 3 2 4 Sample Output 1 16
Sample Input 2 3 3 0 1 1 1 2 1 0 2 1 Sample Output 2 -1
题解
1.首先看到本题,绝大多数人的想法都是DFS,虽然时间复杂度有点高,但是一看数据,又不太,只要我们在深搜中加上记忆化处理,顺便剪一剪枝,也是可以过的
但是!!
我想说的不是DFS,而是带上动规的思想。
如下
(1)(实现需要使用位运算,等会说)
首先假设出一个dp[s][k],s代表已经被扫到的元素的集合,k代表刚进入集合,这里我们是不是就需要列出和k相连的元素(i),然后判定该元素是否已经属于该集合,如果不,就向下扫取最小值
动规方程:
dp[s][i]=min{dp[s+i][i]+d[k][i]}//d数组存储的是图
(2)边界处理
1.记忆化处理,当我们扫到的dp值大于0的时候,说明该点已经被扫过了,直接返回值就好
2.当扫到初始点的时候,直接返回dp=0就好了
(3)元素属于判断以及判断是否扫完每一个元素
首先我们知道每种元素只有在集合中和不在集合中两种状态,所以我们可以依靠二进制位运算来表示每个元素的状态,0为不在,1为在
插入:位运算
其实一共就是五种
通过二进制位来实现
1.&:与 两个二进制位都为1才1
2.|:或 两个二进制位只要有一个为1就为1
3.^:异或 相同为1,相反为0
4.<<左移 将整个数的二进制位左移 右补0 左舍
5.>> 右移 溢出补0 左补0 右舍
这篇文章写得通俗易懂
(4)代码,有详细注释
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#define maxn 99999
using namespace std;
int moving[1<<16][100];
int a[1000][1000];
int b,c,d;
int n,m;
int search(int s,int k)
{
if(moving[s][k]>=0) //这里就是上面所说的记忆化
return moving[s][k];
if(s==(1<<n)-1&&k==0)//如果s已满那么加一就会进位到2的n次方,k等于代表回到起点
return moving[s][k]=0;//已经回到了,不再有值
int num=9999;
for(int i=0;i<n;i++)
{
if(!(s>>i&1))//先判定i点是否加入了集合
{
num=min(num,search(s|1<<i,i)+a[k][i]);
//如果没加入集合就取小,这里因为我们将a数组初始化得大于最大值,所以不用考虑能否到达
}
}
return moving[s][k]=num;
}
int main()
{
//freopen("tsp.in","r",stdin);
//freopen("tsp.out","w",stdout);
memset(moving,-1,sizeof(moving));
memset(a,0x3f,sizeof(a));
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>b>>c>>d;
a[b][c]=d;
}
long long int kk=search(0,0);
if(kk==9999||kk<0)
cout<<"-1";//如果值未更新的话,就相当没有找到
else
cout<<kk;
while(1)
cout<<"love Sabrina"<<endl;//防copy
fclose(stdin);
fclose(stdout);
return 0;
}