【luogu P6880】Bus / 奥运公交 / オリンピックバス(最短路)

时间:2021-08-24
本文章向大家介绍【luogu P6880】Bus / 奥运公交 / オリンピックバス(最短路),主要包括【luogu P6880】Bus / 奥运公交 / オリンピックバス(最短路)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Bus / 奥运公交 / オリンピックバス

题目链接:luogu P6880

题目大意

给你一个有向图,你可以至多将一条边的方向反过来,支付翻转这条边的费用。
问你从 1 到 n,再从 n 到 1 的最小费用。如果不行就输出 -1。

思路

我们考虑枚举每条边看是否翻转。
一开始我们先求出从 \(1\) 出发和从 \(n\) 出发的最短路。
然后我们考虑翻转某条边。

我们把从 \(1\)\(n\) 和从 \(n\)\(1\) 分开来看。
如果它不在最短路上(最短路可以跑的时候记录,要用边记录不要用点,不然会超时因为有重边)那它转了之后可能会比最短路优秀。

红色是新的路径,不难看到还要记录反向边以 \(n\) 为起点的最短路,以处理 \(i\sim n\) 的最短路。
同理,你求 \(n\)\(1\) 的时候还需要反向以 \(1\) 为起点的最短路。

那如果原本在最短路上呢?
那就看起来不太好处理,但是你发现 \(n\) 只有 \(200\),说明你最短路上的就 \(200\) 级别,是可以每条边都暴力翻转,直接暴力重新跑一次 dij 的。
然后复杂度大概就是 \(O(n^3)\),就可以过。

然后时间会有点紧,自己卡一卡,搞点 O2,不用跑的 dij 别跑就可以了。
(由于 \(n\) 比较小,dij 不加堆优化反而更快?)

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

struct node {
	ll x;
	int to, nxt;
	ll cst;
	bool use;
}e[100001];

int n, m, x, y, fr[201], _fr[201], re;
int le[201], KK, now;
ll dis[201], dis_[201], ans, _dis[201], disd[201], z, zz, nl, nr, tmp[201];
bool in[201];
char c;

int read() {
	re = 0; c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re;
}

void add(int x, int y, ll z, ll zz) {
	e[++KK] = (node){z, y, le[x], zz, 1}; le[x] = KK;
	e[++KK] = (node){z, x, le[y], zz, 0}; le[y] = KK;
}

void dij1(bool op) {//正向从 1 跑
	now = 0;
	memset(dis, 127 / 3, sizeof(dis));
	memset(in, 0, sizeof(in));
	dis[1] = 0;
	for (int i = 1; i <= n; i++) {
		now = 0;
		for (int j = 1; j <= n; j++)
			if (!in[j]) now = (dis[now] < dis[j]) ? now : j;
		in[now] = 1;
		
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].use && dis[e[i].to] > dis[now] + e[i].x) {
				dis[e[i].to] = dis[now] + e[i].x;
				if (op) fr[e[i].to] = i;
			}
	}
}

void dij2() {//反向从 1 跑
	now = 0;
	memset(dis_, 127 / 3, sizeof(dis_));
	memset(in, 0, sizeof(in));
	dis_[1] = 0;
	for (int i = 1; i <= n; i++) {
		now = 0;
		for (int j = 1; j <= n; j++)
			if (!in[j]) now = (dis_[now] < dis_[j]) ? now : j;
		in[now] = 1;
		
		for (int i = le[now]; i; i = e[i].nxt)
			if (!e[i].use && dis_[e[i].to] > dis_[now] + e[i].x) {
				dis_[e[i].to] = dis_[now] + e[i].x;
			}
	}
}

void dij3(bool op) {//正向从 n 跑
	now = 0;
	memset(_dis, 127 / 3, sizeof(_dis));
	memset(in, 0, sizeof(in));
	_dis[n] = 0;
	for (int i = 1; i <= n; i++) {
		now = 0;
		for (int j = 1; j <= n; j++)
			if (!in[j]) now = (_dis[now] < _dis[j]) ? now : j;
		in[now] = 1;
		
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].use && _dis[e[i].to] > _dis[now] + e[i].x) {
				_dis[e[i].to] = _dis[now] + e[i].x;
				if (op) _fr[e[i].to] = i;
			}
	}
}

void dij4() {//反向从 n 跑
	memset(disd, 127 / 3, sizeof(disd));
	memset(in, 0, sizeof(in));
	disd[n] = 0;
	for (int i = 1; i <= n; i++) {
		now = 0;
		for (int j = 1; j <= n; j++)
			if (!in[j]) now = (disd[now] < disd[j]) ? now : j;
		in[now] = 1;
		
		for (int i = le[now]; i; i = e[i].nxt)
			if (!e[i].use && disd[e[i].to] > disd[now] + e[i].x) {
				disd[e[i].to] = disd[now] + e[i].x;
			}
	}
}

int main() {
	n = read(); m = read();
	
	for (int i = 1; i <= m; i++) {
		x = read(); y = read();
		z = read(); zz = read();
		add(x, y, z, zz);
	}
	
	dij1(1); dij2(); dij3(1); dij4();
	ans = dis[n] + _dis[1];
	for (int nw = 1; nw <= n; nw++)
		for (int i = le[nw]; i; i = e[i].nxt) {
			if (!e[i].use) continue;
			
			if (!(fr[e[i].to] == i && dis[nw] + e[i].x + disd[e[i].to] == dis[n])) nl = min(dis[n], e[i].x + dis[e[i].to] + disd[nw]);//不在最短路上,看硬要翻转走会不会比最短路优
				else {//在最短路上
					e[i].use = 0; e[i + 1].use = 1;
					for (int i = 1; i <= n; i++) tmp[i] = dis[i];
					dij1(0);//直接换边重新跑 dij,看会不会更优
					nl = dis[n];
					e[i].use = 1; e[i + 1].use = 0;
					for (int i = 1; i <= n; i++) dis[i] = tmp[i];
				}
			if (!(_fr[e[i].to] == i && _dis[nw] + e[i].x + dis_[e[i].to] == _dis[1])) nr = min(_dis[1], e[i].x + _dis[e[i].to] + dis_[nw]);
				else {//与上面同理
					e[i].use = 0; e[i + 1].use = 1;
					for (int i = 1; i <= n; i++) tmp[i] = _dis[i];
					dij3(0);
					nr = _dis[1];
					e[i].use = 1; e[i + 1].use = 0;
					for (int i = 1; i <= n; i++) _dis[i] = tmp[i];
				}
			ans = min(ans, nl + nr + e[i].cst);
		}
	
	if (ans >= dis[0]) printf("-1");
		else printf("%lld", ans);
	
	return 0;
}

原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P6880.html