[LOJ508] 「LibreOJ NOI Round #1」失控的未来交通工具 题解

[LOJ508] 「LibreOJ NOI Round #1」失控的未来交通工具 题解

首先有一个引理,如果可以走 \(a\) 的倍数和 \(b\) 的倍数,那么等价于可以走 \(\gcd(a, b, m)\) 的倍数。

  • 先考虑 \(m\) 是奇数。

如果 \(m\) 是奇数,那么对于一条边 \(w_i > 0\),可以在上面绕圈,可以得到 \((2k + 1)w_i\),因为 \(\gcd(2, m) = 1\),所以 \(\gcd((2k + 1)w_i, m)\) 等价于 \(\gcd(kw_i, m)\),用并查集维护所有边的 \(\gcd\) 即可,最后可行的就是 \(\gcd\) 的倍数,因为如果不想经过一条边可以来回绕 \(2k + 1 = m\) 次消除贡献。

  • \(m\) 是偶数。

考虑一条已确定路径 \(dis\) 在其上加入环的贡献,路径到环的这段路径可以通过走 \(m\) 次消除贡献,环可以随便绕,每绕一次会得到环长贡献,一条边也是可以绕的,不过每绕一次得到 \(2w_i\) 的贡献。

总而言之就是所有环长和 \(2w_i\)\(\gcd\) 的倍数的贡献可以凑出来,再加上任意一条可行路径,最后可以得到的路径长度是 \(gk + dis\) 的。

用带权并查集维护可行路径长度和所有环长 \(\gcd\),每加入一条边,如果这条边成环,将这条边对应的环加入 \(\gcd\),否则只加入 \(2w\)

这样不可以维护所有环的 \(\gcd\),但是在本题的限制下等价,原因类似上面消除贡献的部分,没有计算的环长都可以被已计算的环长表示。

考虑计算答案,即计算有多少对 \((k, p), gk+dis = x+pb\),容易发现这是扩展欧几里得的形式,得到 \(p\) 的一个最小非负整数解,根据扩欧的通解结论,每多 \(\dfrac g{\gcd(g, b)}\) 也是一组解,得到 \(gk + dis\) 之后这些都是可以 \(O(1)\) 计算的。

值得一提的是,偶数奇数不用分开写,因为奇数答案计算方式被偶数的计算方式包含。

// Problem: #508. 「LibreOJ NOI Round #1」失控的未来交通工具
// Author: Moyou
// Copyright (c) 2025 Moyou All rights reserved.

#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <cassert>
#define int long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10;

int n, m, q, fa[N], d[N], Gcd[N];
int find(int x) {
    if(fa[x] == x) return x;
    int p = find(fa[x]);
    d[x] += d[fa[x]];
    fa[x] = p;
    return p;
}
void merge(int a, int b, int c) {
    int x = find(a), y = find(b);
    if(x ^ y) fa[x] = y, d[x] = c - d[a] - d[b], Gcd[y] = __gcd(Gcd[y], __gcd(Gcd[x], c * 2));
    else Gcd[x] = __gcd(Gcd[x], __gcd(c * 2, d[a] + d[b] + c));
}
int exgcd(int a, int b, int &x, int &y) {
    if(!b) return x = 1, y = 0, a;
    int d = exgcd(b, a % b, y, x);
    return y -= a / b * x, d;
}
int floor(int a, int b) {
    return (a - (a % b + b) % b) / b;
}
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m >> q;
    for(int i = 1; i <= n; i ++) fa[i] = i;
    for(int i = 1, op, u, v, w, b, c; i <= q; i ++) {
        cin >> op >> u >> v >> w;
        if(op == 1) merge(u, v, w);
        else {
            w %= m;
            cin >> b >> c;
            int x = find(u), y = find(v);
            if(x != y) {
                cout << 0 << '\n';
                continue;
            }
            Gcd[x] = abs(Gcd[x]);
            int g = __gcd(Gcd[x], m), dis = (d[u] + d[v]) % m, xt, yt, d = exgcd(g, -b, xt, yt);
            if((w - dis) % d) {
                cout << 0 << '\n';
                continue;
            }
            int dt = (w - dis) / d;
            d = abs(d);
            xt *= dt, yt *= dt;
            int k = floor(-yt * d + g - 1, g);
            yt += k * (g / d);
            if(yt >= c) cout << 0 << '\n';
            else cout << floor((c - yt) * d + g - 1, g) << '\n';
        }
    }

    return 0;
}

posted @ 2025-11-11 16:36  MoyouSayuki  阅读(4)  评论(0)    收藏  举报
:name :name