图论思维题
2025-10-23

人类智慧题


A - Train Splitting

https://www.luogu.com.cn/problem/CF1776F

  • 神秘构造题,猜 \(k=2\) 一定能构造
  • 考虑直接从图上抠一个点下来,与之相关的边全部染成 1,剩下的染成 2
  • 发现如果恰好选中了菊花的根就不行;需要找到一个度数不为 \(n-1\) 的点。那完全图怎么办呢,从根上抠一条当颜色 3 即可。
#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    for (std::cin >> T; T--; ) {
        int n, m;
        std::cin >> n >> m;
        std::vector<int> deg(n + 1);
        std::vector<std::pair<int, int> > e(m + 1);
        int pos = -1;
        for (int i = 1; i <= m; ++i) {
            std::cin >> e[i].first >> e[i].second;
            ++deg[e[i].first], ++deg[e[i].second];
        }
        for (int i = 1; i <= n; ++i)
            if (deg[i] != n - 1) {
                pos = i;
                break;
            }
        if (pos != -1) {
            std::cout << 2 << '\n';
            for (int i = 1; i <= m; ++i)
                if (e[i].first == pos || e[i].second == pos)
                    std::cout << 1 << ' ';
                else
                    std::cout << 2 << ' ';
            std::cout << '\n';
        }
        else {
            std::cout << 3 << '\n';
            pos = 1;
            bool flag = true;
            for (int i = 1; i <= m; ++i)
                if (e[i].first == pos || e[i].second == pos) {
                    if (flag)
                        std::cout << 3 << ' ', flag = false;
                    else
                        std::cout << 1 << ' ';
                }
                else
                    std::cout << 2 << ' ';
        }
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

B - Graph Partition

https://www.luogu.com.cn/problem/AT_agc039_b

  • 考虑二分图染色什么时候不行,即有奇环时
  • 考虑有多少种颜色时能染奇环,发现都不行,故二分图染色即可判定。
  • 真的只有绿吗?观察样例,猜测答案为图的直径。考虑构造:

    枚举每个点并钦定其为唯一一个 \(1\),然后依次向下染色。显然最后构造出来的是合法的。最大为直径长度 + 1。

    也可以从差分约束的角度理解,但无甚必要。

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n;
    std::cin >> n;
    std::vector<std::vector<int> > g(n + 1), g1(n + 1, std::vector<int> (n + 1, 0x3f3f3f3f));
    for (int i = 1; i <= n; ++i) {
        g1[i][i] = 0;
        for (int j = 1; j <= n; ++j) {
            char t;
            std::cin >> t;
            if (t == '1')
                g[i].push_back(j), g1[i][j] = 1;
        }
    }
    std::vector<int> col(n + 1);
    std::function<bool(int, int)> DFS = [&](int x, int now) {
        col[x] = now;
        for (auto i : g[x])
            if (!col[i]) {
                if (!DFS(i, 3 - now))
                    return false;
            }
            else if (col[i] == now)
                return false;
        return true;
    };
    if (!DFS(1, 1))
        std::cout << -1 << '\n';
    else {
        for (int k = 1; k <= n; ++k)
            for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= n; ++j)
                    g1[i][j] = std::min(g1[i][j], g1[i][k] + g1[k][j]);
        int mx = 0;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                mx = std::max(mx, g1[i][j] + 1);
        std::cout << mx << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

C - Strange Housing

https://www.luogu.com.cn/problem/CF1470D

  • 第一反应是随便找一个生成树然后二分图染色,但是发现有当且仅当奇环时行为不太正确
  • 考虑打个补丁,抛弃二分图染色的想法,不断尝试染成黑色,如果染了下一个点会出现黑黑边,那就不染它,对得比较显然。
  • 当且仅当不连通时无解。
#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int T;
    for (std::cin >> T; T--; ) {
        int n, m;
        std::cin >> n >> m;
        std::vector<int> f(n + 1);
        std::iota(f.begin() + 1, f.end(), 1);
        std::function<int(int)> find = [&](int x) {
            return x == f[x] ? x : f[x] = find(f[x]);
        };
        std::vector<std::vector<int> > g(n + 1), g1(n + 1);
        int cnt = 0;
        for (int x, y; m--; ) {
            std::cin >> x >> y;
            g1[x].push_back(y), g1[y].push_back(x);
            if (find(x) != find(y)) {
                f[find(x)] = find(y);
                ++cnt, g[x].push_back(y), g[y].push_back(x);
            }
        }
        if (cnt != n - 1) {
            std::cout << "NO\n";
            continue;
        }
        std::vector<int> col(n + 1);
        std::function<void(int, int)> DFS = [&](int x, int fa) {
            for (auto i : g[x])
                if (i != fa) {
                    bool flag = true;
                    for (auto j : g1[i])
                        if (col[j] == 2) {
                            flag = false;
                            break;
                        }
                    if (flag)
                        col[i] = 2;
                    else
                        col[i] = 1;
                    DFS(i, x);
                }
            return;
        };
        col[1] = 2, DFS(1, -1);
        std::vector<int> res;
        for (int i = 1; i <= n; ++i)
            if (col[i] == 2)
                res.push_back(i);
        std::cout << "YES\n" << (int)res.size() << '\n';
        for (auto i : res)
            std::cout << i << ' ';
        std::cout << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

D - Defender of Childhood Dreams

https://www.luogu.com.cn/problem/CF1583F

  • 考虑把每连续 \(k\) 个点分为一个一级组,每个一级组内全部连 \(1\) 边。显然组内边最长不超过 \(k-1\)
  • 接着把每连续 \(k\) 个一级组分为一个二级组,二级组内空闲边全部涂成颜色 \(2\)。此时 \(a<b\) 的条件就会派上用场——每个一级组的值域是没有交的,一级组之间的边都是同一个朝向。这样就保证由颜色为 \(2\) 的边构成的连链来自不同的一级组,长度最多为 \(k-1\)
  • 依次类推,\(i\) 级组是大小为 \(k^i\) 的团,故 \(i\) 最大为 \(\left\lceil \log_kn\right\rceil\)
  • 按上述流程构造,即可 \(O(n^2)\) 完成。
#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    int n, k;
    std::cin >> n >> k;
    using arr = std::vector<int>;
    using brr = std::vector<arr>;
    brr pos;
    for (int i = 1; i <= n; ++i)
        pos.push_back({ i });
    brr g(n + 1, arr(n + 1));
    std::function<void(brr&, int)> calc = [&](brr &pos, int now) {
        if ((int)pos.size() == 1) {
            std::cout << now << '\n';
            for (int i = 1; i <= n; ++i)
                for (int j = i + 1; j <= n; ++j)
                    std::cout << g[i][j] << ' ';
            std::cout << '\n';
            return;
        }
        brr p;
        ++now;
        for (int i = 0; i < (int)pos.size(); i += k) {
            p.emplace_back();
            for (int j = i; j < i + k && j < (int)pos.size(); ++j) {
                for (auto a : p.back())
                    for (auto b : pos[j])
                        g[a][b] = now;
                for (auto b : pos[j])
                    p.back().push_back(b);
            }
        }
        calc(p, now);
        return;
    };
    calc(pos, 0);
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

E - Edge Split

https://www.luogu.com.cn/problem/CF1726D

  • 题意有些许歧义,『红色连通块』其实是指删掉蓝边之后的连通块。考虑到 \(m\le n+2\) 而不是更多,考虑直接分讨。
  • 考虑树的情况,对于任意颜色,删一条边就会带来 \(1\) 个连通块的代价,随便染色即可。
  • 考虑基环树的情况,发现如果环上有某个颜色,那么钦定这个颜色第一次删边删的是环边,就会发现第一次删边不会有代价,故强制环上有两种颜色即可。

    考虑 \(m=n+1\) 的情况,环可能有下面三种形态:

    对于第一、二种,保证两个环上都有两种颜色即可,第三种乍一看有点复杂,其实还是一样的,保证两个环都有两种颜色即可,这样三个环显然都会有两种颜色。站在不饱和度的角度,第三个环其实是无意义的。

  • 由此类推 \(m=n+2\) 的情况,发现只需要在并查集连边时令树边为蓝,反祖边为红即可。发现这样会且仅会在第一组样例的情况出问题:

    如果中间的三角形在最后输入就不能得到最优解
    如果中间的三角形在最后输入就不能得到最优解

    怎么处理这个问题呢,我们使用人类智慧,发现这种情况最后三条红边构成三角形,我们只要发现构造出来的解是这样的三角形,由于造成这一点的顺序很苛刻,故一直 random_shuffle 输入,继续构造直到合法即可。

    (?怎么最优解全是这么做的,我还以为只有我一个人会乱搞。)

  • 当不饱和度为 4 的时候就会出现这种结构:

    此时按照刚刚的方法就一定构造不出合法解了。这或许也是 \(m\le n+2\) 的原因。

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
    const auto stime = std::chrono::steady_clock::now();
#endif
    std::mt19937 rand(0xabcdef);
    int T;
    for (std::cin >> T; T--; ) {
        int n, m;
        std::cin >> n >> m;
        std::vector<int> res(m + 1);
        std::vector<std::tuple<int, int, int> > e(m + 1);
        for (int i = 1, x, y; i <= m; ++i) {
            std::cin >> x >> y;
            e[i] = { x, y, i };
        }
        auto work = [&](std::vector<std::tuple<int, int, int> > &e) {
            std::vector<int> f(n + 1);
            std::iota(f.begin() + 1, f.end(), 1);
            std::function<int(int)> find = [&](int x) {
                return x == f[x] ? x : f[x] = find(f[x]);
            };
            std::set<int> t;
            for (int i = 1; i <= m; ++i) {
                auto [x, y, id] = e[i];
                if (find(x) == find(y)) {
                    t.insert(x), t.insert(y);
                    res[id] = 1;
                }
                else {
                    res[id] = 0;
                    f[find(x)] = find(y);
                }
            }
            return (int)t.size() != 3;
        };
        for (; !work(e); std::shuffle(e.begin() + 1, e.end(), rand));
        for (int i = 1; i <= m; ++i)
            std::cout << res[i];
        std::cout << '\n';
    }
#ifndef ONLINE_JUDGE
    std::cerr << std::fixed << std::setprecision(6) << std::chrono::duration<double> (std::chrono::steady_clock::now() - stime).count() << "s\n";
#endif
    return 0;
}

F - Fair Share

https://www.luogu.com.cn/problem/CF1634E


言论