先看题目:其实就是给定n个点,找出一条从起点回到起点的最短路径,可以多次经过同一个点。
有两个注意的地方:1.最短路径。2.可以多次经过同一个点。
对于1:我们首先应该考虑计算任意两个点之间的最短路,此处由于数据范围较小,可以直接使用floyd算法,好记。如果蓝桥杯中遇到同样的题,而且迪杰斯特拉算法之类的记不清了,可以先用这个,能得部分分也行。
对于2:可能多次经过同一个点。那么就不能简单的用vis数组之类的来判断某一点的状态。所以我们考虑用一个数的二进制表示各个村庄的访问状态,二进制表示的每一位表示对应村庄的访问状态,1表示经过这个村庄了,0表示没经过呢。
好了,我们再接着想,怎么计算最短路径呢,是不是需要比较每条可能路径的长度?那么比较长度,其实就是计算在所有村庄都访问过的状态下,到达某个终点村庄i,然后计算dp[i][(1<<n)-1),也就是从0到i的最短距离。
所以,我们显然要使用状态压缩dp+floyd来完成这道题目。其实我也没想到状压,看的题解。
但是我发现,如果数据范围很小,一般能保证2^n在可遍历的范围内时,基本就和二进制有关,而且如果当前状态可以由上一状态转移过来,那么很可能就是状压dp了。
然后前面的代码就不在这里描述了,主要说一下状压dp转移的那个循环。
我们首先肯定得在第一层循环中遍历所有可能经过的状态:从0->(1<<n)-1,也就是从一个村庄都没访问过的状态到最后访问玩所有的村庄。然后我们遍历这些状态,并在第二层循环中遍历当前状态经过的村庄。那么要注意了:我们在找到达某一个村庄j的最短路径时,其实可以找到一个中转节点k,从0先走一条路到达中转村庄k,然后再从k到达这个村庄j。如果经过这个中转节点可以使得从0到j的路径变短,那么就更新这条最短路。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string.h>
#include<cmath>
#include<vector>
#include<map>
#define ll long long
using namespace std;
const int N = 1e6+5;
const ll mod = 1e9+7;
int m;
int main(){
int n, d;
scanf("%d%d", &n, &d);
int zb[n][2];
for(int i = 0; i < n; i++) scanf("%d%d", &zb[i][0], &zb[i][1]);
double w[n][n];
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
int x = zb[i][0] - zb[j][0];
int y = zb[i][1] - zb[j][1];
double dis = sqrt(x * x + y * y);
if(dis <= d){ //看看能不能直接连上
w[i][j] = w[j][i] = dis;
}else{
w[i][j] = w[j][i] = double(N);
}
}
}
for(int i = 0; i < n; i++){ //弗洛伊德计算最短路
for(int j = 0; j < n; j++){
for(int k = 0; k < n; k++){
if(w[i][k] + w[k][j] < w[i][j]){
w[i][j] = w[i][k] + w[k][j];
}
}
}
}
double dp[n][1 << n]; //从0走到i,中间经过的村庄的集合的二进制表示为j
for(int i = 0; i < n; i++){
for(int j = 0; j < (1<<n); j++){
dp[i][j] = double(N);
}
}
dp[0][1] = 0; //从0走到0
for(int i = 0; i < (1 << n); i++){
for(int j = 0; j < n; j++){ //遍历当前状态经过的村庄
if(((i >> j) & 1) == 1){
for(int k = 0; k < n; k++){ // 除了经过j,还经过k,把k作为i和j之间的中转节点
if((((i - (1 << j)) >> k) & 1) == 1){
//到达j,经过的集合为i--先到达k,经过的集合为i - (1 << j),再从k到j
dp[j][i] = min(dp[j][i], dp[k][i - (1 << j)] + w[k][j]);
}
}
}
}
}
double res = N;
for(int i = 1; i < n; i++){
res = min(res, dp[i][(1 << n) - 1] + w[i][0]);
}
printf("%.2f\n", res);
return 0;
}
//ababc
//5 6 1
//3 4 1 1 2 1
//4 10
//1 1
//5 5
//1 5
//5 1
先这样,出去吃石锅拌饭了!我超爱!