割邊判定法則;
無向邊是橋,當且僅當搜索樹上存在的一個子節點滿足:dfn[u]<low[v]
。
如何理解這個式子:
橋是什麼,橋是兩個單獨島嶼的橋。
什麼是單獨的島嶼,還記得桃花源記裏面的世外桃源嗎。
什麼是世外桃源,就是與外人間隔
。
再者,什麼是追溯值,就是一個節點可以在自己子樹和子樹可以拓展的節點中找到一個最小的編號。
刪掉了橋,那麼在世外桃源裏面任何一個節點都不會再與外面的節點有任何連接。
於是世外桃源內部的所有節點,他們的最小追溯值一定不會小於的編號。
咱們要知道,自給自足是很難成功的,總得有一個人出去買加碘海鹽,那麼這個人就是桃花源的源主。
我們認爲源主就是所有節點中編號最小的節點,也就是有可能與外人有所連接的節點。
換言之,也就是這個橋兩端中,在世外桃源內的節點就是源主。
:
因此,當的時候:
- . 我們發現從節點出發,在不經的前提下,不管走哪一條邊,我們都無法抵達節點,或者比節點更早出現的節點。
- . 此時我們發現所在的子樹似乎形成了一個封閉圈,那麼自然也就是橋了。(得證)。
割點判斷法則:
其實和上面的判斷,只有一點修改。若不是搜索樹的根節點,若節點是割點,那麼當且僅當搜索樹上存在的一個兒子節點,滿足:
"源主"節點:編號最小,所以肯定是最先遍歷到的。既然如此,那麼顯然他的,就是代表最先遍歷到的,必然就是最小的。而且割點是一個世外桃源和外界的唯一通道,所有的兒子節點的都必須大於等於它,不可以小於它,因此得證。
其實證明和上面的正經證明是一模一樣的,只不過多了一個等於號罷了。
另外:如何判斷根是不是割點,必須保證有至少兩個兒子節點,否則不能叫作割點。
模板:
割邊模板:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+20;
int head[N],edge[N<<1],Next[N<<1],tot;
int dfn[N],low[N],n,m,num;
bool bridge[N<<1];
void add_edge(int a,int b) {
edge[++tot]=b;
Next[tot]=head[a];
head[a]=tot;
}
void Tarjan(int x,int Edge) {
dfn[x]=low[x]=++num;//DFS序標記
for(int i=head[x]; i; i=Next[i]) { //訪問所有出邊
int y=edge[i];//出邊
if (!dfn[y]) { //不曾訪問過,也就是沒有標記,可以認爲是兒子節點了
Tarjan(y,i);//訪問兒子節點y,並且設置邊爲當前邊
low[x]=min(low[x],low[y]);//看看能不能更新,也就是定義中的,subtree(x)中的節點 最小值爲low[x]
if (low[y]>dfn[x]) { //這就是橋的判定
bridge[i]=bridge[i^1]=true;//重邊也是橋
}
} else if (i!=(Edge^1)) {
low[x]=min(low[x],dfn[y]);
}//第二類定義,也就是通過1跳不在搜索樹上的邊,能夠抵達 subtree(x)的節點
}
}
int main() {
scanf("%d%d",&n,&m);
tot=1;//邊集從編號1開始
for(int i=1; i<=m; i++) {
int a,b;
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
for(int i=1; i<=n; i++) {
if (!dfn[i]) { //一個無向圖,可能由多個搜索樹構成
Tarjan(i,0);
}
}
for(int i=2; i<=tot; i+=2) { //無向邊不要管,直接跳2格
if (bridge[i]) {
printf("%d %d\n",edge[i^1],edge[i]);//橋的左右兩端
}
}
return 0;
}
割點模板:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+20;
int head[N],edge[N<<1],Next[N<<1],tot;
int dfn[N],low[N],n,m,num,root,ans;
bool cut[N];
void add_edge(int a,int b) {
edge[++tot]=b;
Next[tot]=head[a];
head[a]=tot;
}
void Tarjan(int x) {
dfn[x]=low[x]=++num;//DFS序標記
int flag=0;
for(int i=head[x]; i; i=Next[i]) { //訪問所有出邊
int y=edge[i];//出邊
if (!dfn[y]) { //不曾訪問過,也就是沒有標記,可以認爲是兒子節點了
Tarjan(y);//訪問兒子節點y,並且設置邊爲當前邊
low[x]=min(low[x],low[y]);//看看能不能更新,也就是定義中的,subtree(x)中的節點 最小值爲low[x]
if (low[y]>=dfn[x]) { //這就是割點的判定
flag++;//割點數量++
if (x!=root || flag>1) { //不能是根節點,或者說是根節點,但是有至少兩個子樹節點 是割點
cut[x]=true;
}
}
} else low[x]=min(low[x],dfn[y]); //第二類定義,也就是通過1跳不在搜索樹上的邊,能夠抵達 subtree(x)的節點
}
}
int main() {
scanf("%d%d",&n,&m);
memset(cut,false,sizeof(cut));
for(int i=1; i<=m; i++) {
int a,b;
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
for(int i=1; i<=n; i++) {
if (!dfn[i]) { //一個無向圖,可能由多個搜索樹構成
root=i,Tarjan(i);
for(int i=1; i<=n; i++) { //統計割點個數
if (cut[i]) {
ans++;
}
printf("%d\n",ans);
for(int i=1; i<=n; i++) { //順序遍歷,康康哪些點是割點
if (cut[i]) {
printf("%d ",i);
}
}
}
}
}
return 0;
}