Loading

[图灵杯 2025] T2. Bougainvillea

前言

利用率
看看时间
下课上课都别颓了, 好好搞, 下课干点自己想干的事
缓慢耐心

思路

\(w = 0\) 时, 相当于询问是否能通过 \(0\) 权边联通 \(u, v\), 这是好做的

考虑特殊性质

\(o = 1\)

显然直接用 \(w = 0\) 的方法即可

\(o = 2\)

相当于询问能否找到一条 \(u, v\) 之间的偶数长度路径
首先, \(u, v\) 应当在一个联通块内, 我们任意找到这个联通块中的一个奇环, 假设奇环上有一个点 \(c\), 那么我们找一个 \(u \to c \to v\) 的路径, 如果这个路径已经是偶数就已经存在, 否则填上奇环上的边也存在

总的来讲, 我们只需要判断这个联通块是否有奇环即可, 如果有那么必定存在, 如果没有证明这是一个二分图, 二分图的一个性质是相同集合之间只有偶数路径, 不同集合之间只有奇数路径, 判断一下即可
另外二分图的集合划分可以用黑白染色来实现, 是唯一确定的

\(o = 3\)

首先考虑 \(w\)
\(w = 0\) 的情况已经讨论过了
\(w = 1\) 的情况等价于联通
\(w \geq 2\) 的情况下, 不妨令 \(w = kx (k\,\)是奇数\()\), 那么此时我们只需要在 \(w = x\) 的答案上往返 \(k\) 次即可

于是问题进一步简化为, \(w = 2^k (k \geq 1)\), 询问是否存在 \(u \to v\) 的长度为 \(2^k\) 倍数的路径
考虑能不能构造出 \(u \to v\) 的长度为 \(2^k\) 倍数的路径

注意到直接对路径做乘法不太行了, 考虑对路径做加法
考虑现在我们有一条长度为偶数的简单路径 \(u \to v\), 因为如果其为奇数, 我们任意调整只能往上加偶数\((\)必定来回\()\)
于是现在我们有 \(len \equiv 2l \pmod{2^k}\), 显然我们可以知道 \(k\) 是偶数, 于是我们一定可以通过挂一条 \(2t \times w\) 的路径上去, 使得 \(len + 2tw \equiv 2l + (2tw \bmod 2^k) \pmod{2^k}\)

不难发现我们需要使得 \(2tw \bmod 2^k = 2^k - 2l\), 也就是 \(2tw \equiv 2^k - 2l \pmod{2^k}\)
根据模运算的性质, \(tw \equiv 2^{k - 1} - l \pmod{2^{k - 1}}\), 这是一个经典的一元线性同余方程, 我们不难发现只要 \(\gcd(w, 2^{k - 1}) \mid 2^{k - 1} - l\) 就一定有解
显然当 \(w, 2^{k - 1}\) 互质时一定有解, 也就是 \(w\) 为奇数一定有解, 不难发现如果存在奇数边就行了

如果不存在呢?
考虑现在所有边都是偶数边, 不妨强制使得每一条边除以所有边的 \(\gcd\), 这样一定存在奇数边, 然后在类似的做即可

完整解法

问题进一步简化为, \(w = 2^k (k \geq 1)\), 询问是否存在 \(u \to v\) 的长度为 \(2^k\) 倍数的路径

先二分图染色, 如果无法染色存在奇环
否则, 如果在不同集合一定无解, 否则存在奇边
还是考虑只要 \(\gcd(w, 2^{k - 1}) \mid 2^{k - 1} - l\) 就一定有解, 那么把奇边吃下去就好了


以上是题解做法, 现在开始参考着自己做一遍
首先不连通的情况提前判断

\(o = 1\)

因为 \(0\) 的倍数仅有 \(0\), 所以我们需要用 \(0\) 权边将 \(u, v\) 连通, 直接并查集可以做到 \(\mathcal{O} (m \log n + q)\)
这是简单的

\(o = 2\)

考虑要找一个经过边数为偶数的路径
套路的想到二分图

二分图染色失败

此时存在奇环
我们设奇环上一点 \(c\), 那么我们找到一条 \(u \to c \to v\) 的路径, 如果它已经是偶数长度的, 那么存在, 否则在奇环上绕一圈, 把它变成偶数的
因此无论如何都存在

二分图染色成功

二分图染色之后, 同集合之间只有偶数长度的路径, 不同集合之间只有奇数长度的路径
直接判断即可


题目中的性质

对于 w=0w = 0, 等价于 00 权边连通
对于 w=1w = 1, 等价于联通
对于 w2w \geq 2, 不妨令 w=kx(kw = kx (k\,是奇数)), 那么此时我们只需要在 w=xw = x 的答案上往返 kk 次即可, 等价于 w=xw = x
不难发现任意问题可以被尽量拆成 w=k2xw = k 2^x, 我们只需要解决 w=2xw = 2^x 的问题


\(o = 3\)

考虑此时是在森林上的情况

性质

对于简单路径 uvu \to v, 我们想要走到其他的点, 一定需要来回((此时不存在环路))
因此可以把问题看做在 uvu \to v 的简单路径((下面称为始路))上添加一些路径, 这些路径都来回走若干遍之后挂上去

因此如果始路的长度为 lsl_s, 挂上去的路径总长度为 lal_a, 那么总长度为 ls+2lal_s + 2tl_a

我们只处理 \(w = 2^x\) 的问题, 其他情况要么转化, 要么直接解决
也就是求 \(l_s + 2l_a \equiv 0 \pmod{2^x}\) 的存在性

\(l_s\) 为奇数时直接无解, 否则需要使得 \(2l_a \equiv -l_s \pmod{2^x}\)
我们现在需要找到合法的 \(\displaystyle l_a = \sum_{r \in \mathbb{L}} t_rl_r\), 为了方便处理, 我们可以转化成假设有一条路径长度为 \(l_r\), 那么我们可以使 \(l_a \gets l_a + t_rl_r\) 从而使得 \(2l_a \equiv -l_s \pmod{2^x}\)

注意到此时 \(t_rl_r\) 对式子的影响为 \(2t_rl_r \bmod 2^x\)
事实上我们不难观察到, 这一条路径就足够构造答案了

\[ \begin{gather*} 2t_rl_r \equiv -l_s \pmod{2^x} \\ \Downarrow \\ \gcd(2l_r, 2^x) \mid l_s \\ \Downarrow \\ \gcd(l_r, 2^{x-1}) \;\bigg|\; \frac{l_s}{2} \end{gather*} \]

一种简单的构造方式是找到一个奇数 \(l_r\)
不存在仅当所有的树边长度均为偶数

一个很精妙的想法是, 把所有边表示为 \(k \times 2^p\), 其中 \(k\) 为奇数, 不妨找到所有边的 \(p\) 的最小值 \(p_m\), 然后把每条边变成 \(k \times 2^{p - p_m}\) 并把问题转化为 \(w = 2^{x - p_m}\) 的问题
不难证明此时一定存在奇数边并且与原问题完全等价

正解做法

考虑现在我们还是任取一条 \(u \to v\) 的路径, 记作始路, 长度为 \(l_s\)
现在我们的增路有两种情况: 偶环\((\)包含了路径来回走的情况\()\)和奇环
先进行一次二分图染色简化问题

二分图染色失败

此时存在奇环
我们设奇环上一点 \(c\), 那么我们找到一条 \(u \to c \to v\) 的路径作为始路, 长度为 \(l_s\)
现在我们要使得 \(l_s + l_a \equiv 0 \pmod{2^x}\), 等价于找到 \(l_a \equiv -l_s \pmod{2^x}\) 的存在性

不难发现设奇环长度为 \(l_c\), 我们可以把 \(l_a \gets l_a + l_c\), 对式子的影响是 \(l_c \bmod 2^x\)
不难发现一个奇环就足够构造答案了

\[ \begin{gather*} t_cl_c \equiv -l_s \pmod{2^x} \\ \text{because of } \gcd(l_c, 2^x) \mid l_s \text{, exist }t_c \end{gather*} \]

因此此时必然有解

二分图染色成功

如果不在一个集合, \(u, v\) 之间一定只存在奇数路径, 无解
否则始路的长度 \(l_s\) 一定是偶数, 我们想要找到 \(l_a \equiv -l_s \pmod{2^x}\) 的存在性

因为不存在奇环, 所以不妨修改成 \(2l_a \equiv -l_s \pmod{2^x}\), 注意后面的 \(l_a\) 定义改变
现在我们的增路 \(l_c\) 贡献为 \(2l_c \bmod 2^x\), 注意后面的 \(l_c\) 定义改变

类似 \(o = 3\) 中的分析可以得到, 只要存在 \(l_c\) 为奇数即可
类似的, 如果存在一条奇数边 \((\mu, \vartheta)\), 我们首先找到 \(u \to \mu \to v\) 的路径, 然后再在这条奇数边上来回走即可

如果不存在奇数边, 我们仍然可以类似 \(o = 3\) 中的做法, 把它强行变成一个拥有奇数边的问题

实现

首先把问题转化成一定有奇数边的情况
然后二分图染色, 做一下就好了

参考代码
#include<bits/stdc++.h>

using namespace std;

typedef long long i64;
constexpr i64 max_bit = 1LL << 62;
int n, m, o;
struct Edge {
    int v; i64 w;
};
vector<vector<Edge>> G;

vector<int> comp, comp0, col, conf, vis;
// component id, color, conflict
vector<i64> g(n + 1);
// lowbit of all edges in the component
int cur;

i64 lowbit(i64 x) {
    return x & -x;
}

void dfs(int u) {
    comp[u] = cur;
    for(auto [v, w]: G[u]) {
        if(w != 0) {
            i64 x = lowbit(w);
            g[cur] = min(g[cur], x);
        }
        if(!comp[v]) {
            dfs(v);
        }
    }
}

void dfs1(int u) {
    vis[u] = 1;
    for(auto [v, w]: G[u]) {
        if(!vis[v]) {
            col[v] = col[u] ^ (int)((w / g[cur]) & 1);
            dfs1(v);
        } else if(col[v] != (col[u] ^ (int)((w / g[cur]) & 1))) {
            conf[cur] = 1;
        }
    }
}

void dfs0(int u) {
    comp0[u] = cur;
    for(auto [v, w]: G[u]) {
        if(w == 0 && !comp0[v]) dfs0(v);
    }
}

void prework() {
    comp0.resize(n + 1);
    for(int i = 1; i <= n; i++) {
        if(!comp0[i]) cur++, dfs0(i);
    }

    cur = 0, comp.resize(n + 1), vis = col = conf = comp;
    g.resize(n + 1, max_bit);
    for(int i = 1; i <= n; i++) {
        if(!comp[i]) {
            cur++, dfs(i);
            if(g[cur] != max_bit) {
                dfs1(i);
            }
        }
    }
}

bool query(int u, int v, i64 w) {
    if(w == 0) {
        return comp0[u] == comp0[v];
    }
    if(comp[u] != comp[v]) {
        return false;
    }
    if(w & 1) {
        return true;
    }

    int id = comp[u];
    i64 x = lowbit(w);
    if(x <= g[id] || conf[id]) {
        return true;
    }
    return col[u] == col[v];
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);

    int q;
    cin >> n >> m >> q >> o;
    G.resize(n + 1);
    for(int i = 1, u, v; i <= m; i++) {
        i64 w;
        cin >> u >> v >> w;
        G[u].push_back({v, w}), G[v].push_back({u, w});
    }

    prework();

    for(int i = 1, u, v; i <= q; i++) {
        i64 w;
        cin >> u >> v >> w;
        cout << (query(u, v, w) ? "bougain" : "villea") << '\n';
    }

    return 0;
}

总结

图上奇偶性往往要想到二分图

  • 二分图染色失败的时候, 一定存在奇环
  • 二分图染色成功的时候, 一定存在奇边

一元线性同余方程和二元线性丢番图方程是等价的

全为偶数这一性质: \(\gcd\) 一定 \(>1\) 且为偶数, 如果全体除以这个 \(\gcd\)\(2\) 的幂次部分, 那么剩下一定存在奇数

posted @ 2025-06-02 10:57  Yorg  阅读(36)  评论(0)    收藏  举报