洛谷:P2620 虫洞(离散化 + 最短路递归建边)

虫洞

在这里插入图片描述

此题看似很裸,离散出有用的点,在点和点之间建边跑最短路

但这是错的!

观察可以发现:

  • 对于 x — > y x —> y x>y 的边,x 肯定不能是虫洞起点,因为在起点就会被吸入进虫洞传到终点;
  • 除此之外 y 是起点或终点不会影响,但 x — > y x—>y x>y 的过程中,我们想尽可能跑时间短,肯定每秒跑 S 步,如果跑着的过程中某时刻刚好触碰到一个虫洞的起点,我们肯定会被传送走,直接算的 x — > y x—>y x>y 的距离是不正确的
  • 如何正确计算出保证在不被传送走的情况下的两点间距离?
  • 数据较小,所以这里用了一个递归处理:
int getmi(int x, int y) {
	if (x == y)return 0;//递归边界,两点重合返回0
	if (isbegin(x)) return INF;//若x是起点,这个距离肯定不能计算
	int yy = y;
	//be从小到大排序
	for (int i = 0; i < be.size(); i++)//在存起来的所有是起点的点中
		if (be[i] > x && be[i] < yy && (be[i] - x) % S == 0) {
			yy = be[i];//找到第一个在范围内同时 S 步走的过程中会误入的虫洞
			break;
		}
	while (yy != y && isbegin(yy))yy--;//此处是起点同时不是终点就往后退一步(不踩到虫洞)(终点是虫洞没关系)
	if (yy == x)return INF;//若退回起点证明无路可走了
	return ceil((double)(yy - x) / S) + getmi(yy, y);
	//否则从 x走到yy(上取整),然后再递归计算 yy到y 这段的距离
}

C o d e Code Code

#include<bits/stdc++.h>
#include<unordered_set>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define ul (u << 1)
#define ur (u << 1 | 1)
//#define x first
//#define y second
//#pragma GCC optimize(2)
//[博客地址](https://blog.csdn.net/weixin_51797626?t=1) 
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;
typedef pair <ll, PII> PI;

const int N = 110, M = 200010, MM = N;
int INF = 0x3f3f3f3f, mod = 100003;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int g[N][N];
map<int, int> mp;
vector<int> be, d;
vector<PII> pa;

bool isbegin(int x) { //判断是否为起点
	auto t = lower_bound(be.begin(), be.end(), x);
	if (t == be.end())return false;
	return *t == x;
}

int find(int x) { //找虫洞对
	return lower_bound(d.begin(), d.end(), x) - d.begin();
}

int getmi(int x, int y) { //递归求距离(细节很多)
	if (x == y)return 0;
	if (isbegin(x)) return INF;
	int yy = y;
	for (int i = 0; i < be.size(); i++)
		if (be[i] > x && be[i] < yy && (be[i] - x) % S == 0) {
			yy = be[i];
			break;
		}
	while (yy != y && isbegin(yy))yy--;
	if (yy == x)return INF;
	return ceil((double)(yy - x) / S) + getmi(yy, y);
}

void floyd() { //数据小,跑floyd都行
	for (int k = 0; k < n; k++)
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}

int main() {
	cinios;

	while (cin >> D, D) {

		cin >> S >> k;
		mem(g, 0x3f);
		mp.clear();//多组重置
		be.clear(), d.clear(), pa.clear();
		n = 0;
		//map用来离散去重
		mp[0] = ++n, mp[D] = ++n;//起点终点记录

		for (int i = 1; i <= k; i++) {
			int a, b;
			cin >> a >> b;
			if (!mp[a])mp[a] = ++n;
			if (!mp[b])mp[b] = ++n;

			pa.push_back({ a,b });//记录虫洞对
			be.push_back(a);//记录起点
		}
		sort(be.begin(), be.end());//起点从小到大排序

		int t = 0;
		for (auto j : mp)d.push_back(j.first);//取出离散点
		sort(d.begin(), d.end());//再从小到大排序

		for (auto j : pa)//排过序的点中找到虫洞对,虫洞不花费时间
			g[find(j.first)][find(j.second)] = 0;

		for (int i = 0; i < n; i++)//从小到大的点枚举两两连边
			for (int j = i + 1; j < n; j++)
				g[i][j] = min(g[i][j], getmi(d[i], d[j]));

		floyd();
		cout << g[0][n - 1] << '\n';
	}

	return 0;
}
/*
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值