生活随笔
收集整理的這篇文章主要介紹了
D8 双连通分量
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
記得有個梗那一天,zw學生zzh大佬說逃不掉的路變成a不掉的題哈哈哈哈;
分離的路徑:
BZOJ 1718
POJ 3177
LUOGU 286;
思路:在同一個邊雙連通分量中,任意兩點都有至少兩條獨立路可達,所以同一個邊雙連通分量里的所有點可以看做同一個點。縮點后,新圖是一棵樹,樹的邊就是原無向圖的橋。
現在問題轉化為:在樹中至少添加多少條邊能使圖變為雙連通圖。
結論:添加邊數=(樹中度為1的節點數+1)/2;
具體方法為,首先把兩個最近公共祖先最遠的兩個葉節點之間連接一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,因為一個形成的環一定是雙連通的。
然后再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,恰好是(leaf+1)/2次,把所有點收縮到了一起。
點評:
邊雙連通分量縮點后,原圖變成一顆真正的樹,而樹上各種操作可以和其他知識點結合起來。
這種敏感性要有,比如縮點之后就可以快速求必經邊,必經點之類的。
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+
10;
template<typename T>inline
void read(T &
x)
{x=
0;T f=
1,ch=
getchar();while (!isdigit(ch)) ch=
getchar();if (ch==
'-') f=-
1, ch=
getchar();while (isdigit(ch)) x=(x<<
1)+(x<<
3)+(ch^
48), ch=
getchar();x*=
f;
}
int ver[maxn<<
1],Next[maxn<<
1],head[maxn],len=
1;
inline void add(
int x,
int y)
{ver[++len]=y,Next[len]=head[x],head[x]=
len;
}
int dfn[maxn],low[maxn],id;
bool bridge[maxn<<
1];
inline void tarjan(
int x,
int inedge)
{dfn[x]=low[x]=++
id;for (
int i=head[x];i;i=
Next[i]){int y=
ver[i];if (!
dfn[y]){tarjan(y,i);low[x]=
min(low[x],low[y]);if (low[y]>
dfn[x])bridge[i]=bridge[i^
1]=
1;}else if (i!=(inedge^
1))low[x]=
min(low[x],dfn[y]);}
}
int c[maxn],Out[maxn],dcc;
inline void dfs(
int x)
{c[x]=
dcc;for (
int i=head[x];i;i=
Next[i]){int y=
ver[i];if (c[y] || bridge[i])
continue;dfs(y);}
}
int main()
{freopen("rpaths.in",
"r",stdin);freopen("rpaths.out",
"w",stdout);int n,m;read(n);read(m);for (
int i=
1;i<=m;++
i){int x,y;read(x);read(y);add(x,y);add(y,x);}for (
int i=
1;i<=n;++
i)if (!dfn[i]) tarjan(i,-
1);for (
int i=
1;i<=n;++
i)if (!c[i]) ++
dcc,dfs(i);for (
int i=
2;i<=len;i+=
2){int x=ver[i^
1],y=
ver[i];if (c[x]!=
c[y])++Out[c[x]],++
Out[c[y]];}int ans=
0;for (
int i=
1;i<=dcc;++
i)if (Out[i]==
1) ++
ans;printf("%d\n",(ans+
1)>>
1);return 0;
} View Code 第二題:逃不掉的路
題目描述
現代社會,路是必不可少的。任意兩個城鎮都有路相連,而且往往不止一條。但有些路連年被各種XXOO,走著很不爽。按理說條條大路通羅馬,大不了繞行其他路唄——可小擼卻發現:從a城到b城不管怎么走,總有一些逃不掉的必經之路。
他想請你計算一下,a到b的所有路徑中,有幾條路是逃不掉的?
輸入格式
第一行是n和m,用空格隔開。
接下來m行,每行兩個整數x和y,用空格隔開,表示x城和y城之間有一條長為1的雙向路。
第m+2行是q。接下來q行,每行兩個整數a和b,用空格隔開,表示一次詢問。
輸出格式
對于每次詢問,輸出一個正整數,表示a城到b城必須經過幾條路。
樣例輸入
5 5
1 2
1 3
2 4
3 4
4 5
2
1 4
2 5
樣例輸出
0
1
樣例解釋
第1次詢問,1到4的路徑有 1–2--4 ,還有 1–3--4 。沒有逃不掉的道路,所以答案是0。
第2次詢問,2到5的路徑有 2–4--5 ,還有 2–1--3–4--5 。必須走“4–5”這條路,所以答案是1。
數據約定與范圍
共10組數據,每組10分。
有3組數據,n ≤ 100 , n ≤ m ≤ 200 , q ≤ 100。
另有2組數據,n ≤ 103, n ≤ m ≤ 2 x 103 , 100 < q ≤ 105。
另有3組數據,103 < n ≤ 105 , m = n-1 , 100 < q ≤ 105。
另有2組數據,103 < n ≤ 105 , n ≤ m ≤ 2 x 105 , 100 < q ≤ 105。
對于全部的數據,1 ≤ x,y,a,b ≤ n;對于任意的道路,兩端的城市編號之差不超過104;
任意兩個城鎮都有路徑相連;同一條道路不會出現兩次;道路的起終點不會相同;查詢的兩個城市不會相同。
?
分析:既然是求必須經過的邊,那么邊雙包含的集合肯定不是必須經過的,就可以把所有邊雙縮點;
原圖得到一顆樹后,問題就變成了簡單的求樹上兩點之間的距離。
這個題就是lca+邊雙的題啦:
?第三題:礦場搭建
BZOJ 2730
LUOGU 3225
首先我們知道,對于這張圖,我們可以枚舉坍塌的是哪個點,對于每個坍塌的點,最多可以將圖分成若干個不連通的塊,這樣每個塊我們可能需要一個出口才能滿足題目的要求,枚舉每個坍塌的點顯然是沒有意義的,我們只需要每個圖的若干個割點,這樣除去割點的圖有若干個塊,我們可以求出只與一個割點相連的塊,這些塊必須要一個出口才能滿足題目的要求,每個塊內有塊內個數種選法,然后將所有滿足一個割點相連的塊的點數連乘就行了。
對于每個與一個割點相連的塊必須建出口可以換一種方式理解,我們將每個塊看做一個點,那么算上割點之后,這張圖就變成了一顆樹,只有葉子節點我們需要建立出口,因為對于非葉子節點我們不論斷掉哪個點我們都有另一種方式相連,這里的葉子節點就是與一個割點相連的塊。
最后還有個特判,就是對于一個雙連通圖,我們至少需要選取兩個點作為出口,因為如果就選一個,可能該點為坍塌點,這時我們就任選兩個點就行了,方案數為點數x(點數-1)>>1。
代碼該如何寫?
先tarjan求一下所有的點雙。
然后對于每一個點雙,分類討論:
1、只有一個割點,必須選一個非割點。
2、有>=2個割點,不用選
3、有0個割點,必須選倆。
畫個圖理解一下撒;
#include<bits/stdc++.h>
using namespace std;
const int maxn=
510;
struct gg
{int y,next;
}a[maxn*
maxn];
template<typename T>inline
void read(T &
x)
{x=
0;T f=
1,ch=
getchar();while (!isdigit(ch)) ch=
getchar();if (ch==
'-') f=-
1, ch=
getchar();while (isdigit(ch)) x=(x<<
1)+(x<<
3)+(ch^
48), ch=
getchar();x*=
f;
}
long long hl,ans=
1;
int lin[maxn],len;
inline void add(
int x,
int y)
{a[++len].y=
y;a[len].next=
lin[x];lin[x]=
len;
}
int dfn[maxn],low[maxn],num,root;
bool cut[maxn];
inline void tarjan(
int x,
int fa)
{dfn[x]=low[x]=++
num;int flag=
0;for (
int i=lin[x];i;i=
a[i].next){int y=
a[i].y;if (!
dfn[y]){tarjan(y,x);low[x]=
min(low[x],low[y]);if(low[y]>=
dfn[x]){++
flag;if (x!=root||flag>
1) cut[x]=
1;}}else if (y!=
fa)low[x]=
min(low[x],dfn[y]);}
}
int vis[maxn],k,cnt;
inline void dfs(
int x)
{vis[x]=
k;++
cnt;for(
int i=lin[x];i;i=
a[i].next){int y=
a[i].y;if(vis[y]!=k&&cut[y]) ++num,vis[y]=
k;if(!
vis[y]) dfs(y);}
}
int main()
{freopen("input.in",
"r",stdin);freopen("output.out",
"w",stdout);for (
int ca=
1;;++
ca){int n=
0,m;read(m);if(!m) exit(
0);memset(dfn,0,
sizeof(dfn));memset(vis,0,
sizeof(vis));memset(cut,0,
sizeof(cut));memset(lin,0,
sizeof(lin));hl=k=num=len=
0;ans=
1;for (
int i=
1;i<=m;++
i){int x,y;read(x);read(y);n=
max(n,max(x,y));add(x,y);add(y,x);}for (
int i=
1;i<=n;++
i)if(!dfn[i]) root=
i,tarjan(i,i);for (
int i=
1;i<=n;++
i)if(!vis[i]&&!
cut[i]){++k,cnt=num=
0;dfs(i);if(!num) hl+=
2,ans*=cnt*(cnt-
1)/
2;if(num==
1) hl++,ans*=
cnt;}printf("Case %d: %lld %lld\n",ca,hl,ans);}return 0;
} View Code ?
轉載于:https://www.cnblogs.com/Tyouchie/p/10426016.html
總結
以上是生活随笔為你收集整理的D8 双连通分量的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。