無向圖中任意添加一條邊,使得橋(割邊)數量最少。含重邊(重邊構成邊雙連通分量)。
用前向星存邊,標記訪問過的邊(雙向同時標記)。重邊自然在同一分量。關鍵:不使用 father 判斷,改用邊訪問標記。
struct Edge { int to, next; } e[2000500];
int head[300010], ecnt;
bool vis_edge[2000500];
int dfn[300010], low[300010], timer;
int stk[300010], top, scc[300010], scc_cnt;
void tarjan(int u) {
dfn[u] = low[u] = ++timer;
stk[++top] = u;
for (int i=head[u]; i!=-1; i=e[i].next) {
if (vis_edge[i]) continue;
int v = e[i].to;
vis_edge[i] = vis_edge[i^1] = true; // 標記雙向
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
++scc_cnt;
int v;
do {
v = stk[top--];
scc[v] = scc_cnt;
} while (v != u);
}
}
int head2[300010], ecnt2;
int lev[300010];
void add_edge2(int u, int v) { ... } // 重建樹
void bfs(int s) {
memset(lev, 0, sizeof(lev));
queue q; q.push(s); lev[s]=1;
while (!q.empty()) {
int u=q.front(); q.pop();
for (int i=head2[u]; i!=-1; i=e2[i].next) {
int v=e2[i].to;
if (!lev[v]) { lev[v]=lev[u]+1; q.push(v); }
}
}
}
int main() {
while (scanf("%d%d", &n, &m) == 2 && (n||m)) {
// 初始化、讀邊、建原圖
tarjan(1);
// 重建樹:遍歷原圖邊,不同 scc 連邊
for (int u=1; u<=n; u++)
for (int i=head[u]; i!=-1; i=e[i].next) {
int v=e[i].to;
if (scc[u] != scc[v]) add_edge2(scc[u], scc[v]);
}
bfs(1);
int u = max_element(lev+1, lev+scc_cnt+1) - lev;
bfs(u);
int diam = *max_element(lev+1, lev+scc_cnt+1) - 1;
printf("%d\n", scc_cnt - 1 - diam);
}
}
#pragma comment(linker, "/STACK:..."))。此題融合了「邊雙連通縮點」「樹直徑」「橋優化」三大圖論核心概念,是進階圖論的綜合訓練佳作。