1. 程式人生 > >p1691 [hdj3394_點雙聯通+橋]railway

p1691 [hdj3394_點雙聯通+橋]railway

題目

描述 Description
有一個公園有n個景點,這n個景點由m條無向道路連線而成。公園的管理員準備規劃一一些形成迴路的參觀路線。如果一條道路被多條參觀路線公用,那麼這條路是衝突的;如果一條道路沒在任何一個迴路內,那麼這條路是多餘的道路。
問分別有多少條有衝突的路和多餘的路
輸入格式 Input Format
包括多組資料
每組資料第一行2個整數n,m
接下來m行,每行2個整數x,y,表示從x到y有一條無向邊。
輸入資料以n=0,m=0結尾
輸出格式 Output Format
一行2個整數,表示你要求的多餘的道路和衝突的道路的數量。
樣例輸入 Sample Input

8 10
0 1
1 2
2 3
3 0
3 4
4 5
5 6
6 7
7 4
5 7
0 0

樣例輸出 Sample Output

1 5
時間限制 Time Limitation
1s
註釋 Hint
【資料範圍】
n<=10000
m<=100000
0<=x,y<n
每個測試點有10組資料。
來源 Source
hdoj 3394
題面+資料來自2018級 宋逸群

題解

查了半天,是道橋與點雙的板子題(然而我打了邊雙。。。。。。。。)。。。。。。。
一.解釋一下點雙:點雙連通分量:對於一個連通圖,如果任意兩點至少存在兩條“點不重複”的路徑,則說這個圖是點雙連通的,簡單來說就是任意兩條邊都在同一個簡單環中,即內部無割頂。

二.先嚐試解釋一下要求輸出的兩個東西究竟是什麼:
1.多餘邊:不在任何環中,一定是橋。
2.衝突邊:如果一個環內的邊數大於點數,那麼這個環內所有邊都是“衝突邊”。其實就是點雙了。

三.怎麼判斷一個雙連通分量中環的個數呢?根據點數跟邊數的關係 。
1.當點數=邊數,形成一個環 。
2.當點數>邊數(一條線段,說明這條邊是橋) 。
3.當點數<邊數,那麼就含1個以上的環了。

程式碼

簡陋註釋,可能有錯,歡迎大家指出!!!!!!

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
const
int maxm=10010; inline int read() { int f=1,num=0; char ch=getchar(); while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); } while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar(); return num*f; } int bridge,vDCC; int ver[maxn*2],Next[maxn*2],head[maxm],len; inline void add(int x,int y) { ver[++len]=y,Next[len]=head[x],head[x]=len; } int belong[maxm],cnt=0; bool instack[maxm]; void GetvDCC() { int num=0; for (int j=0;j<cnt;++j) { int x=belong[j];//屬於第0個點雙的點 for (int i=head[x];i!=-1;i=Next[i])//鄰接表的dfs { int y=ver[i]; if (instack[y]) num++;//如果說他被訪問過,那他肯定不在棧裡,也就說是點雙了 } } num>>=1;//無向圖,所以點會過兩次 if (num>cnt) vDCC+=num;//如果一個環內的邊數大於點數,那麼這個環內所有邊都是“衝突邊” } int Stack[maxm],top; int dfn[maxm],low[maxm],id=0; void tarjan(int x,int fa)//有自環時不加自環的邊 { //點雙連通縮點方法:清空路徑,列舉ver,Next陣列中儲存的路徑,建立雙向邊 low[x]=dfn[x]=++id; Stack[top++]=x; for (int i=head[x];i!=-1;i=Next[i])//此處的判斷條件需要注意 { int y=ver[i]; if (y==fa) continue; //如果這條邊的另一個結點正好是這個結點的雙親(相當於就是從這條邊過來的,沒必要再走一次) if (!dfn[y])//y結點還未訪問過 { tarjan(y,x);//訪問y結點 low[x]=min(low[x],low[y]);//更新low if (low[y]>dfn[x]) ++bridge;//正如上面所說:當點數>邊數(一條線段,說明這條邊是橋) if (low[y]>=dfn[x]) { int k; cnt=0; memset(instack,0,sizeof(instack));//注意在進行標記前要把它清空 do { k=Stack[--top];//取出棧頂 belong[cnt++]=k;//k屬於第cnt個點雙 instack[k]=1;//他已不在棧裡 }while (k!=y); belong[cnt++]=x; instack[x]=1; GetvDCC(); } } else//與有向圖區分,此處else不需要判別v節點是否在棧內 low[x]=min(low[x],dfn[y]); } } int main() { while (1) { int n=read(),m=read(); if (!n && !m) break; memset(head,-1,sizeof(head)); //注意這裡,所以下面tarjan函式裡的for迴圈裡的判斷條件為i!=-1 len=0; while (m--) { int x=read(),y=read();//結點是從零開始的 add(x,y),add(y,x);//無向圖 } memset(dfn,0,sizeof(dfn)); bridge = vDCC = id = top = 0; for (int i=0;i<n;++i) if (!dfn[i]) tarjan(i,-1); printf("%d %d\n",bridge,vDCC); } return 0; }

刷題是一種出路,列舉是一種思想,打表是一種勇氣,搜尋是一種信仰,剪枝是一種精神,騙分是一種日常。——來自高一大佬