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\{ \begin{aligned} dis[v]-dis[u]≥1\\ dis[u]-dis[v]≥-9\\ \end{aligned} \right. \]

根据差分约束的经验,我们从 \(u\)\(v\) 连一条边权为 \(1\) 的边, 从 \(v\)\(u\) 连一条边权为 \(-9\)的边跑最长路即可。

如果 \(SPFA\) 跑出来的是负环,那么无解,否则每条边的长度为 \(dis[v]-dis[u]\)(现在可见 \(dis\) 代表最长路径)

细节实现

首先你知道知道我们只要 \(1~n\) 的路径,所以我们正反图各跑一遍,把那些 \(1\) 不能到达的节点和 \(n\) 不能到达的节点剔除掉,剩下的节点就是我们真正要进行约束的节点,而那些被剔除掉的节点对应的边可以随便乱赋值的啦……

这个操作可以通过两遍 \(dfs\) 或者 \(bfs\) 实现。但是由于我只建了一张图,所以我采用正反边异或后编号相等的原则。

这样我只写了一个 \(dfs\) 函数

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\) ……崩崩

正确的写法:

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\)

接下来看代码理解吧

\(Code:\)

#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", 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\n", dis[e[i].y] - dis[e[i].x]);
        else printf("1\n");
    }
    return 0;
}

写在后面

当时考试脑抽没写出来,然后因为那个 \(op\) 改了一个下午……

跪了 \(Orz\)

猜你喜欢

转载自www.cnblogs.com/Ning-H/p/11668193.html