Luogu P5590 赛车游戏 题解

Luogu P5590 赛车游戏 题解

写在前面

众所周知这是一篇题解,当然这也是一篇经验的总结。

它源自于洛谷月赛,传送门:P5590 赛车游戏

笔者写下这篇题解,一是希望自己这次的错误不要再犯,二是希望能帮助大家。

题解部分

题面简析

题意大致可以概括为:给你 (n) 个点 (m) 条边的 一张图,你需要给每条边加上边权,使得(1-n)的所有路径的长度均相等。

现在感觉问题简单多了,我们可以想到暴力地添加边权(反正边权也只有 (1-9)

解题思路

很明显上面的办法是不能拿满分的。并且我拿到本题并没有想过要打暴力。

我们假设这张图存在两个顶点 (u,v),它们之间的边权为 (val(u,v))

那么就有:(dis[u]+val(u,v)=dis[v])(dis)数组是节点(1)到其他点的路径长度最值)

至于这个最值是什么,稍后再解答。

我提出了上面那个式子,那么很明显我们要求的是 (val(u,v)),这东西一定满足 (1≤val(u,v)≤9)

好了,现在变形一下式子:(1≤val(u,v)=dis[v]-dis[u]≤9),看出来什么了吗?

你仔细看看:(1≤dis[v]-dis[u]≤9),差分约束?

没错,就是差分约束,约束条件:$$left{
egin{aligned}
dis[v]-dis[u]≥1
dis[u]-dis[v]≥-9
end{aligned}
ight.

[ 根据差分约束的经验,我们从 $u$ 向 $v$ 连一条边权为 $1$ 的边, 从 $v$ 向 $u$ 连一条边权为 $-9$的边跑最长路即可。 如果 $SPFA$ 跑出来的是负环,那么无解,否则每条边的长度为 $dis[v]-dis[u]$(现在可见 $dis$ 代表最长路径) ## 细节实现 首先你知道知道我们只要 $1~n$ 的路径,所以我们正反图各跑一遍,把那些 $1$ 不能到达的节点和 $n$ 不能到达的节点剔除掉,剩下的节点就是我们真正要进行约束的节点,而那些被剔除掉的节点对应的边可以随便乱赋值的啦…… 这个操作可以通过两遍 $dfs$ 或者 $bfs$ 实现。但是由于我只建了一张图,所以我采用正反边异或后编号相等的原则。 这样我只写了一个 $dfs$ 函数 ~~~cpp void check(int x, bool *flag, int op) { if(flag[x]) return; flag[x] = 1; for(int i = head[x]; i; i = Next[i]) { if(~i & op) continue; int y = ver[i]; check(y, flag, op); } } ~~~ 但是 $op$ 只有 $0$ 和 $1$,与上 $0$ ……崩崩 正确的写法: ~~~cpp void check(int x, bool *flag, int op) { if(flag[x]) return; flag[x] = 1; for(int i = head[x]; i; i = Next[i]) { if((i & 1) == op) continue; int y = ver[i]; check(y, flag, op); } } ~~~ 然后要注意特判 $1$ 不能到 $n$ 的情况,也要输出 $-1$。 接下来看代码理解吧 <font size = 5> $Code:$ </font> ~~~cpp #include<bits/stdc++.h> #define ll long long using namespace std; /* 约束条件 d[a] + val<a,b> = d[b] 1 <= d[b] - d[a] <= 9 */ const int N = 1e5 + 10, M = N << 2; int head[N], Next[M], ver[M]; int edge[M], cnt = 1; struct rec { int x, y; } e[M >> 1]; void add(int x, int y, int v) { ver[++cnt] = y, edge[cnt] = v; Next[cnt] = head[x], head[x] = cnt; } void Add(int x, int y) { add(x, y, 0), add(y, x, 0); } bool v1[N], v2[N]; void check(int x, bool *flag, int op) { if(flag[x]) return; flag[x] = 1; for(int i = head[x]; i; i = Next[i]) { if((i & 1) == op) continue; int y = ver[i]; check(y, flag, op); } } int dis[N], tot[N]; bool v[N]; void spfa(int n) { memset(dis, ~0x7f, sizeof dis), dis[1] = 0; queue<int> q; q.push(1), v[1] = 1; while(q.size()) { int x = q.front(); q.pop(), v[x] = 0; for(int i = head[x]; i; i = Next[i]) { int y = ver[i], val = edge[i]; if(dis[y] < dis[x] + val) { dis[y] = dis[x] + val; if(!v[y]) q.push(y), tot[y]++, v[y] = 1; if(tot[y] > n) puts("-1"), exit(0); } } } if(dis[n] < 0) puts("-1"), exit(0); } void clear() { memset(head, 0, sizeof head); memset(Next, 0, sizeof Next); memset(ver, 0, sizeof ver); cnt = 0; } bool is[N]; int main() { int n, m; scanf("%d %d", &n, &m); for(int i = 1; i <= m; i++) { scanf("%d %d", &e[i].x, &e[i].y); Add(e[i].x, e[i].y); } check(1, v1, 1), check(n, v2, 0); for(int i = 1; i <= n; i++) if(v1[i] && v2[i]) is[i] = 1; is[1] = is[n] = 1; clear(); for(int i = 1; i <= m; i++) { if(is[e[i].x] && is[e[i].y]) { add(e[i].x, e[i].y, 1); add(e[i].y, e[i].x, -9); } } spfa(n); printf("%d %d ", n, m); for(int i = 1; i <= m; i++) { printf("%d %d ", e[i].x, e[i].y); if(is[e[i].x] && is[e[i].y]) printf("%d ", dis[e[i].y] - dis[e[i].x]); else printf("1 "); } return 0; } ~~~ # 写在后面 当时考试脑抽没写出来,然后因为那个 $op$ 改了一个下午…… 跪了 $Orz$]

原文地址:https://www.cnblogs.com/Ning-H/p/11668193.html