原题:http://acm.hdu.edu.cn/showproblem.php?pid=5418
题意:T个测试样例,有n个点(1~n),m条距离不一定相同的线将他们链接起来,求从1出发将每个点都至少访问一次再回到1的最短距离。
这道题是货郎担的模板题,在n很小的时候,可以使用状态压缩dp来解决。
假如不太了解状态压缩dp的可以百度一下或者我之前写过博客讲状态压缩dp的:https://blog.csdn.net/weixin_43191865/article/details/88235731
我们用v表示点,s表示状态,s的二进制码中1表示没有去过的点,0表示去过的点
dp[v][s]:到达点v并且状态为s所需要的最短距离,那么最终状态则是 dp[1][0]
转移方程:(m数组存距离)
dp[v][s]=min(dp[v][s],dp[j][s-{j}]+m[i][j])
写成代码就是:(为什么j要+1,因为我的城市是从1开始的,可以结合后面代码来看)
dp[i][statu]=min(dp[i][statu],dp[j+1][statu|(1<
那么我在做这道题的时候有几个问题,我们不妨先来看一下代码,代码之后我会给出我自己的思考
#include
using namespace std;
const int maxn=20;
const int INF=0x3f3f3f3f;
int m[maxn][maxn],dp[maxn][1<>T;
while(T--){
scanf("%d %d",&n,&q);
memset(m,INF,sizeof(m)); //Fload 需要这个,不然会错
for(int i=1;i<=q;i++){
int fro,to;
scanf("%d %d",&fro,&to);
int why;
scanf("%d",&why);
m[fro][to]=min(m[fro][to],why); //漏了这个WA了N次,为什么要加这个 ?
m[to][fro]=m[fro][to];
}
for(int i=1;i<=n;i++) m[i][i]=0; //漏了这句
Flody(); //为什么要加弗洛伊德算法
memset(dp,INF,sizeof(dp));
dp[1][(1<=0;statu--){ //遍历状态不会导致有些状态无法转移吗?
for(int i=1;i<=n;i++){
for(int j=0;j>j & 1)){
dp[i][statu]=min(dp[i][statu],dp[j+1][statu|(1<
1.为什么在存边的时候需要加min 函数
一开始我没注意到这个点,题目的数据 n<=16,边最多也就16*16,但是题目给m的范围是m<100000,说明可能有重复给的边
2.为什么要弗洛伊德算法
题目没有说明每个点只能访问一遍,假如题目说明每个点只能访问一次,那么就不可以用(我认为)
3.使用for遍历状态的时候,不会导致有状态没有发生转移吗
比如A=min(A,B+M[A][B]),会不会因为B没有被先处理过(B=INF)而导致A没有成功转移呢
首先状态的遍历顺序是从大到小,我们使用1表示没去过,0表示去过。
状态转移间 的两个状态除了要转移的那个位,其他位都保持一样。
而状态转移的那个位是从1转移到0(0由1转移而来)
结合遍历顺序(从大到小),不会出现没有成功转移的情况
4.为什么那个dp转移方程不可以
两个dp方程之间
前者是:后面状态从前面状态转移过来
后者是:前面状态从后面状态转移过来
结合遍历顺序可知,后者行不通(可能说的有点蒙,但是结合状态statu遍历顺序可以想一想)