【并查集】判环,深搜

一、题目大意

Problem - 1726D - Codeforces

给定连通无向无权图,n 个点 m 条边,满足 (n-1 < m < n+2)。 每条边染红色 (1) 或蓝色 (0):

  • 只看红边,连通块数量 (c_1)
  • 只看蓝边,连通块数量 (c_2) 构造染色方案,使 (c_1+c_2) 尽可能小,输出长度 m 的 01 串。

二、核心理论思路(文字描述)

  1. 最优解一定能让其中一种颜色构成生成树 我们选择把 DFS 生成树全部染红,此时红色子图整张连通,(c_1=1),只需要最小化蓝色连通块 (c_2)。
  2. 原图最多 3 条非树回边 树边数量固定 (n-1),多余边最多 3 条,全部初始染蓝色。
  3. 蓝色出现环仅有一种情况 只有 3 条多余边恰好构成三角形(3 个点,每个点蓝色度数都为 2)时,蓝色存在环;其余情况蓝色无环,直接最优。
  4. 消除蓝色环的修正操作 找到 DFS 树上深度最大的那个三角形顶点:
  • 该点连父节点的树边(红)改成蓝色
  • 该点对应的蓝色回边改成红色 修改后:红色依旧是连通图(生成树 + 一条回边),蓝色三角形被破坏(c_2) 下降,总和达到最小值。

三、关键概念

  1. DFS 树边:遍历过程经过的边,构成生成树,初始染红 1;
  2. 回边:不参与 DFS 遍历的多余边,连接祖先与后代,初始染蓝 0;
  3. 三元环判定:蓝色边恰好 3 个顶点,每个顶点在蓝色子图度数都是 2。

四、完整带注释代码

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define endl '\n'

// 邻接表 g[u] = {v, 边编号}
vector<vector<pair<int, int>>> g;
vector<int> dep, par; // dep点深度,par点在DFS树上父节点
vector<bool> vis;     // DFS访问标记
string s;             // 答案01串,0蓝 1红

// DFS构建生成树,树边标记为'1'
void dfs(int u) {
    vis[u] = 1;
    for (auto [v, idx] : g[u]) {
        if (vis[v]) continue;
        dep[v] = dep[u] + 1;
        par[v] = u;
        s[idx] = '1'; // 树边染红
        dfs(v);
    }
}

int main() {
    // cin/cout加速同步流
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t; cin >> t;
    while (t--) {
        int n, m; cin >> n >> m;
        // 清空容器,重置图
        g.assign(n + 1, {});
        vector<pair<int, int>> edges(m); // 保存每条边两端点
        s.assign(m, '0'); // 初始所有边默认蓝色0
        dep.resize(n + 1); par.resize(n + 1); vis.assign(n + 1, 0);

        // 读入所有边,建双向邻接表
        for (int i = 0; i < m; i++) {
            int u, v; cin >> u >> v;
            edges[i] = {u, v};
            g[u].emplace_back(v, i);
            g[v].emplace_back(u, i);
        }
        // 从1号点DFS生成树
        dfs(1);

        // 统计蓝色边每个点的度数
        map<int, int> cnt;
        for (int i = 0; i < m; i++)
            if (s[i] == '0')
                cnt[edges[i].first]++, cnt[edges[i].second]++;

        // 仅当蓝色只有3个点时才可能形成三角形环
        if (cnt.size() == 3) {
            int mn = 1e9, mx = 0;
            // 找蓝色点最小、最大度数
            for (auto [_, c] : cnt) mn = min(mn, c), mx = max(mx, c);
            // 三点度数全为2,蓝色构成三元环,需要修正
            if (mn == mx && mn == 2) {
                vector<pair<int, int>> can;
                // 存入{深度, 点编号}
                for (auto [v, _] : cnt) can.emplace_back(dep[v], v);
                // 从大到小排序,取DFS深度最大的点
                sort(can.rbegin(), can.rend());
                int u = can[0].second, i, j;
                // 找到该点一条蓝色回边、一条连父节点的红色树边
                for (auto [v, idx] : g[u]) {
                    if (s[idx] == '0') i = idx;
                    else if (v == par[u]) j = idx;
                }
                // 交换两条边颜色,消除蓝色环
                s[i] = '1', s[j] = '0';
            }
        }
        // 输出最终染色方案
        cout << s << endl;
    }
    return 0;
}

五、代码执行流程

  1. 初始化答案串全 0(全部默认蓝色);
  2. DFS 遍历整张图,遍历经过的树边改为 1(红色生成树);
  3. 遍历所有蓝色边,统计每个点在蓝色子图的度数;
  4. 判断蓝色是否为三元环:三点、每个点度数都是 2;
  5. 若是三元环,交换最深点的一条回边与父树边的颜色;
  6. 输出 01 字符串。

六、正确性说明

  1. 红色始终连通:要么完整生成树,要么生成树 + 一条回边,(c_1=1);
  2. 修正后蓝色无环,连通块数量最小;
  3. 题目限制最多 3 条多余边,仅三元环一种坏情况,其余无需处理;
相关推荐
To_OC18 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
2401_8414956419 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
fqbqrr19 小时前
2607C++,soui与安卓
c++·soui
fqbqrr1 天前
2607C++,使用微软detours勾挂工具
c++
爱刷碗的苏泓舒1 天前
PPP-AR 中的参考星选取:数学原理、评价指标与切换处理
算法·gnss·模糊度固定·ppp-ar·星间单差·参考星·卫星端偏差
蓝悦无人机1 天前
C++基础 — 函数总结
开发语言·c++
烬羽1 天前
递归老写崩?一个"退回"公式,把回溯题变成填空题
javascript·深度学习·算法
闪电悠米1 天前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法
WL学习笔记1 天前
堆(数据结构)
数据结构·