补给【第十一届】【决赛】【研究生组】

先看题目:其实就是给定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

先这样,出去吃石锅拌饭了!我超爱!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值