Bus / 奥运公交 / オリンピックバス
题目链接:luogu P6880
题目大意
给你一个有向图,你可以至多将一条边的方向反过来,支付翻转这条边的费用。
问你从 1 到 n,再从 n 到 1 的最小费用。如果不行就输出 -1。
思路
我们考虑枚举每条边看是否翻转。
一开始我们先求出从 (1) 出发和从 (n) 出发的最短路。
然后我们考虑翻转某条边。
我们把从 (1) 到 (n) 和从 (n) 到 (1) 分开来看。
如果它不在最短路上(最短路可以跑的时候记录,要用边记录不要用点,不然会超时因为有重边)那它转了之后可能会比最短路优秀。
红色是新的路径,不难看到还要记录反向边以 (n) 为起点的最短路,以处理 (isim 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;
}