题意
在一张n个点,m条边的无向图中允许再加一条边,问增加后图中最少还有多少条割边。\((1≤N,M≤10^5)\)
分析
要注意这样一个情况,一般求割边在不是和网络流有关的情况下都是和双连通分量有关的。因为是割边,因此我们按照边双连通分量缩点,得到一个森林,此时森林里的每一个边都是割边。
然后接下来考虑增加边。在森林间的树之间增加边是很憨憨的一个行为,这只会增加桥;于是就是在树上加边。为了减少树上的割边,我们希望能够把树上最长路径的端点连接起来——也就是直径。
问题于是解决。
代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define MP make_pair
#define MS(x, y) memset(x, y, sizeof(x))
#define rep(i, a, b) \
for (repType i = static_cast<repType>(a); i <= static_cast<repType>(b); ++i)
#define REP(i, a, b) \
for (repType i = static_cast<repType>(a); i < static_cast<repType>(b); ++i)
#define per(i, a, b) \
for (repType i = static_cast<repType>(a); i >= static_cast<repType>(b); --i)
#define PER(i, a, b) \
for (repType i = static_cast<repType>(a); i > static_cast<repType>(b); --i)
#define ALL(x) x.begin(), x.end()
using namespace std;
using repType = signed;
using ll = long long;
using ld = long double;
using pi = pair<int, int>;
const int MAXN = 100005;
struct Edge {
int u, v, w;
bool bri;
Edge() = default;
Edge(int u, int v, int w = 1) : u(u), v(v), w(w), bri(false) {}
};
vector<Edge> edges;
array<vector<int>, MAXN> G, bcc;
int pre[MAXN], iscut[MAXN], bccno[MAXN], dfs_clk, bcc_cnt, bri_cnt;
void add_edge(int u, int v) {
edges.emplace_back(Edge(u, v));
G[u].emplace_back(int(edges.size() - 1));
}
stack<Edge> S;
int tarjan(int u, int fa) {
int lowu = pre[u] = ++dfs_clk;
int child = 0;
for (auto i : G[u]) {
auto &e = edges[i];
auto v = e.v;
if (!pre[v]) {
S.push(e);
child++;
int lowv = tarjan(v, u);
lowu = min(lowu, lowv);
if (lowv > pre[u]) { // 桥
bri_cnt++;
edges[i].bri = edges[i ^ 1].bri = true;
}
if (lowv >= pre[u]) { // 割点
iscut[u] = true;
bcc_cnt++;
bcc[bcc_cnt].clear(); // 从1开始
for (;;) {
auto x = S.top();
S.pop();
if (bccno[x.u] != bcc_cnt) {
bcc[bcc_cnt].emplace_back(x.u);
bccno[x.u] = bcc_cnt;
}
if (bccno[x.v] != bcc_cnt) {
bcc[bcc_cnt].emplace_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (x.u == u && x.v == v) break;
}
}
} else if (pre[v] < pre[u] && v != fa) {
S.push(e);
lowu = min(lowu, pre[v]);
}
}
if (fa < 0 && child == 1) iscut[u] = false;
return lowu;
}
void find_bcc(int n) {
MS(pre, 0);
MS(iscut, 0);
MS(bccno, 0);
dfs_clk = bri_cnt = bcc_cnt = 0;
REP(i, 0, n) {
if (!pre[i]) tarjan(i, -1);
}
}
int nG_cnt, nG_edge_cnt;
array<int, MAXN> nG_clr;
array<vector<int>, MAXN> nG;
array<unordered_set<int>, MAXN> nG_hash;
array<bool, MAXN> nG_vis;
array<int, MAXN> nG_dist;
void dfs1(int x) { // 将图分成若干个边-双连通分量
nG_clr[x] = nG_cnt;
for (auto i : G[x]) {
auto &e = edges[i];
if (nG_clr[e.v] != -1 || e.bri) continue;
dfs1(e.v);
}
}
void build_nG() {
for (auto e : edges) {
int u = nG_clr[e.u], v = nG_clr[e.v];
if (u != v && nG_hash[u].find(v) == nG_hash[u].end()) {
nG_edge_cnt++;
nG_hash[u].insert(v);
nG_hash[v].insert(u);
nG[u].emplace_back(v);
nG[v].emplace_back(u);
}
}
}
pi dfs2(int x, int fa) { // 返回从x出发的最深 <距离, 点>
nG_vis[x] = true;
if (fa != -1)
nG_dist[x] = nG_dist[fa] + 1;
else
nG_dist[x] = 1;
auto ret = MP(nG_dist[x], x);
for (auto v : nG[x]) {
if (v != fa) {
auto pa = dfs2(v, x);
if (pa > ret) {
ret = pa;
}
}
}
return ret;
}
void init(int n) {
edges.clear();
rep(i, 0, n) {
G[i].clear();
nG[i].clear();
nG_hash[i].clear();
}
nG_cnt = nG_edge_cnt = 0;
fill(ALL(nG_clr), -1);
fill(ALL(nG_vis), false);
}
signed main() {
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
init(n);
rep(i, 1, m) {
int u, v;
cin >> u >> v;
add_edge(u, v);
add_edge(v, u);
}
find_bcc(n);
rep(i, 1, n) {
if (nG_clr[i] == -1) {
nG_cnt++;
dfs1(i);
}
}
build_nG();
int ans = 0;
rep(i, 1, nG_cnt) {
if (!nG_vis[i]) {
auto pnt = dfs2(i, -1).second;
ans = max(ans, dfs2(pnt, -1).first - 1);
}
}
cout << nG_edge_cnt - ans << endl;
}
return 0;
}