题目链接:戳我~
题目描述 Description
某乡有n个村庄,有一个售货员,他要到各个村庄去售货,各村庄之间的路程s是已知的,且A村到B村与B村到A村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。
输入描述 Input Description
村庄数n和各村之间的路程(均是整数)
输出描述 Output Description
最短的路程
样例输入 Sample Input
3
0 2 1
1 0 2
2 1 0
样例输出 Sample Output
3
第一次打状压DP,以前只是听说不敢打…… 状压的思想核心还是比较易懂的
核心:
把状态压缩为一个二进制序列,用十进制保存,二进制中每一位都代表着不同的意义,比如说10,它的二进制是1010,可以理解为4号位和2号位的目标达成,1号和3号的目标没有达成。
压缩状态的目的是省空间,因为如果每一种状态开一个多维数组的话太占空间,并且转移也不好写,状压后转移写起来很方便,因为是二进制,主要通过位运算来实现它……
回到题目,看到n <= 15想到状压可以做,状压用在这里就是最外层从1for到2^n,这样就达到了枚举所有状态的目的,(具体实现详见代码),然后核心DP转移方程如下
dp[j][((1 << j-1) | i)] = min(dp[j][((1 << j-1) | i)],dp[k][i] + map[k][j]);
dp[i][j]表示的是从起点到i号点在j状态下的最短路径。
上面式子有一个条件,j没有走过而k走过(看不懂的话,下面有完整代码),那么上面式子的意思就是:从起点到j,状态从原来的i二进制第j-1项为0转移到为1时,取它本身和从k点状态i(即还没有走到j这个村子,因为此时i中对应的j的状态点为0),加上k到j的距离,这里要仔细想想。(位运算的优先级很低,(1 << j-1)优先算减法,然后再算<<,<<的意思是将原来的数字再二进制中左移多少位,1 << j-1位的含义是2^(j-1),2的j-1次方)。
最后时进行这一步:
for(int i = 2;i <= n;i ++)
{
ans = min(ans,dp[i][(1<<n)-1] + map[i][1]);
}
这一步ans初值是INF,然后dp数组表示的是从起点到i点并且除了起点所有的村庄都走的最小值,然后再加上从i点到起点的距离,再所有情况中取最小值就是答案~
下面附上完整代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<deque>
#include<algorithm>
#include<cmath>
using namespace std;
const int size = 124500;
int dp[23][size];
int map[23][23];
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= n;j ++)
scanf("%d",&map[i][j]);
memset(dp,63,sizeof(dp));
dp[1][1] = 0;
for(int i = 0;i <= (1 << n);i ++)
{
for(int j = 1;j <= n;j ++)
{
if(((1 << j-1) & i) == 0)
{
for(int k = 1;k <= n;k ++)
{
if(((1 << k-1) & i))
{
dp[j][((1 << j-1) | i)] = min(dp[j][((1 << j-1) | i)],dp[k][i] + map[k][j]);
}
}
}
}
}
int ans = 2147483640;
for(int i = 2;i <= n;i ++)
{
ans = min(ans,dp[i][(1<<n)-1] + map[i][1]);
}
printf("%d",ans);
return 0;
}