传送门 Codeforces 19E Fairy
若图中不存在非二分图的连通分量,则任意一边删除后仍是二分图;若图中存在大于一个非二分图的连通分量,则不可能通过删除一条边使之变为二分图。故讨论仅存在一个非二分图的连通分量的情况即可。
考虑 D F S DFS DFS 树,将树上节点进行二分图染色。对于非树边,若其端点颜色相同,则出现奇环,称之为非法的非树边;反之为合法的非树边。
若删除的边 e e e 为非树边,当且仅当它是图中唯一的非法的非树边。充分性显然;必要性,使用反证法,若存在多条非法的非树边,删除 e e e 后不会影响其他非法的非树边与树边构成的奇环,此时不构成二分图,证毕。
若删除的边 e e e 是树边,当且仅当树边被所有非法非树边覆盖,且不被任何合法非树边覆盖(覆盖,即被非树边两端点所在的树上路径所包含)。删除 e e e 后, D F S DFS DFS 树划分为子树与其余部分。图存在合法染色方案,当且仅当将子树染色黑白交换能够得到合法方案,分别讨论非法/合法非树边即可得证。
D F S DFS DFS 树的非树边一定连接某个节点与其祖先节点,那么可以在构造 D F S DFS DFS 树的过程中进行类似树上点差分并求前缀和的操作,统计树边被非法/合法非树边覆盖的次数。
总时间复杂度 O ( m ) O(m) O(m)。
#include <bits/stdc++.h> using namespace std; #define rep(i, l, r) for (int i = l, _ = r; i < _; ++i) #define pb push_back const int MAXN = 1E4 + 5; int N, M, dep[MAXN], conf, rec_id; int sum[2][MAXN]; bool chs[MAXN], used[MAXN]; struct edge { int to, id; }; vector<edge> G[MAXN]; void dfs(int u, int p, int d) { dep[u] = d; for (auto &e : G[u]) { int v = e.to; if (dep[v] == -1) { dfs(v, u, d + 1); rep(i, 0, 2) sum[i][u] += sum[i][v]; } else if (v != p) { if (dep[u] < dep[v]) continue; int d = (dep[u] - dep[v]) & 1; ++sum[d ^ 1][u], --sum[d ^ 1][v]; if (d ^ 1) ++conf, rec_id = e.id; } } } void judge(int u) { used[u] = 1; for (auto &e : G[u]) { int v = e.to; if (!used[v]) { judge(v); if (sum[0][v] == 0 && sum[1][v] == conf) chs[e.id] = 1; } } } int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> N >> M; rep(i, 0, M) { int u, v; cin >> u >> v; --u, --v; G[u].pb({v, i}), G[v].pb({u, i}); } memset(dep, -1, sizeof(dep)); rep(u, 0, N) if (dep[u] == -1) dfs(u, -1, 0); rep(u, 0, N) if (!used[u]) judge(u); if (conf == 1) chs[rec_id] = 1; if (conf == 0) { cout << M << '\n'; rep(i, 0, M) cout << i + 1 << (i + 1 == M ? '\n' : ' '); } else { int t = 0; rep(i, 0, M) if (chs[i])++ t; cout << t << '\n'; rep(i, 0, M) if (chs[i]) cout << i + 1 << (!--t ? '\n' : ' '); } return 0; }