1. 程式人生 > >圖論算法之(割點)

圖論算法之(割點)

連通 當前 ont spa space col oid 必須 scanf

從一到題說起:

所謂割點,就是一個連通無向圖中,刪除某一點和與它連接的所有的邊後,剩下的點不再連通,則這個點是關節點。
題目:給定無向圖的點數(N),邊數(M),以及M條邊,輸出圖的所有關節點,以由到大輸。
N<=100000,M<=300000
樣例:
輸入:
10 17
2 1
2 6
2 8
3 2
3 5
4 2
4 7
5 3
5 4
6 3
7 1
7 2
7 3
7 5
8 2
9 6
10 8
輸出:
3
2 6 8
樣例第一行為N和M,接下來M行為M條邊。輸出第一行為割點個數,接下來由小到大輸出割點的編號。

一看到這道題,就想,把任意一個點給去掉,然後遍歷一次,看是否位連通圖,如果不是,就是割點。

但是這樣的復雜度是O(n(n+m))嚴重超時

好吧,我們務必要鉆研出dfs的特性,使之在線性時間,即O(n+m)時間內求出割點

第一我們知道在遍歷時一定會出現割點吧,這不是廢話嗎

然後我們想根節點的成為割點的條件,必須是有>2個兒子節點才可以吧(*^▽^*)

然後,就是在搜索時,怎麽判斷一個點就是割點呢

定理:在無向圖的連通圖G中,當且僅當一個點u存在一個可遍歷的後代節點v無法連回一個比u更老的節點時,這個點u就一個割點

證明:考慮u的任意子節點v。如果v及其還帶無法找到一個更老的節點,那麽無論如何都無法連回去,反過來,如果存在,那麽就一定可以通過那個連回去的點繼續連通,則u不是割點。

證畢。

有人說,有可能從當前點連回一個已經出棧的點,不可能,為什麽:因為一旦出棧,那麽說明他可以搜到的節點已經搜畢。因為這是無向圖,那麽這個點一定不會連到一個出棧的點上去。

完啦。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100000+10
using namespace std;
int head[N],num;
struct edge{
    int next,to;
};
edge e[2*N*(N-1)];
void add(int from,int to)
{
    e[++num].next=head[from];
    e[num].to=to;
    head[from]=num;
}
int flag[N],dfn[N],low[N]; int tim=0,tot; int root; void dfs(int u,int fa) { //vis[u]=1; tim++; low[u]=dfn[u]=tim; int son=0; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; //if(v==fa)continue; if(!dfn[v]) { son++; dfs(v,u); low[u]=min(low[u],low[v]); if(u!=root&&low[v]>=dfn[u]) flag[u]=1; if(u==root&&son>=2) flag[u]=1; } else if(v!=fa) { low[u]=min(low[u],dfn[v]); } } return; } int main() { int n,m; scanf("%d%d",&n,&m); int x,y; for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } root=1; dfs(1,root); int cnt=0; for(int i=1;i<=n;i++) { if(vis[i]==1)cnt++; //printf("%d ",i); } printf("%d\n",cnt); for(int i=1;i<=n;i++) { if(flag[i]==1)printf("%d ",i); } return 0; }

圖論算法之(割點)