一、题目大意
给定连通无向无权图,n 个点 m 条边,满足 (n-1 < m < n+2)。 每条边染红色 (1) 或蓝色 (0):
- 只看红边,连通块数量 (c_1)
- 只看蓝边,连通块数量 (c_2) 构造染色方案,使 (c_1+c_2) 尽可能小,输出长度 m 的 01 串。
二、核心理论思路(文字描述)
- 最优解一定能让其中一种颜色构成生成树 我们选择把 DFS 生成树全部染红,此时红色子图整张连通,(c_1=1),只需要最小化蓝色连通块 (c_2)。
- 原图最多 3 条非树回边 树边数量固定 (n-1),多余边最多 3 条,全部初始染蓝色。
- 蓝色出现环仅有一种情况 只有 3 条多余边恰好构成三角形(3 个点,每个点蓝色度数都为 2)时,蓝色存在环;其余情况蓝色无环,直接最优。
- 消除蓝色环的修正操作 找到 DFS 树上深度最大的那个三角形顶点:
- 该点连父节点的树边(红)改成蓝色
- 该点对应的蓝色回边改成红色 修改后:红色依旧是连通图(生成树 + 一条回边),蓝色三角形被破坏(c_2) 下降,总和达到最小值。
三、关键概念
- DFS 树边:遍历过程经过的边,构成生成树,初始染红 1;
- 回边:不参与 DFS 遍历的多余边,连接祖先与后代,初始染蓝 0;
- 三元环判定:蓝色边恰好 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;
}
五、代码执行流程
- 初始化答案串全 0(全部默认蓝色);
- DFS 遍历整张图,遍历经过的树边改为 1(红色生成树);
- 遍历所有蓝色边,统计每个点在蓝色子图的度数;
- 判断蓝色是否为三元环:三点、每个点度数都是 2;
- 若是三元环,交换最深点的一条回边与父树边的颜色;
- 输出 01 字符串。
六、正确性说明
- 红色始终连通:要么完整生成树,要么生成树 + 一条回边,(c_1=1);
- 修正后蓝色无环,连通块数量最小;
- 题目限制最多 3 条多余边,仅三元环一种坏情况,其余无需处理;