Day8 雙連通分量
文章目錄
A. BZOJ 1718: [Usaco2006 Jan] Redundant Paths 分離的路徑
題目
題解
思路:在同一個邊雙連通分量中,任意兩點都有至少兩條獨立路可達,所以同一個邊雙連通分量裡的所有點可以看做同一個點。
縮點後,新圖是一棵樹,樹的邊就是原無向圖的橋。
現在問題轉化為:在樹中至少新增多少條邊能使圖變為雙連通圖。
結論:
新增邊數=(樹中度為1的節點數+1)/2 具體方法為,首先把兩個最近公共祖先最遠的兩個葉節點之間連線一條邊,這樣可以把這兩個點到祖先的路徑上所有點收縮到一起,因為一個形成的環一定是雙連通的。
然後再找兩個最近公共祖先最遠的兩個葉節點,這樣一對一對找完,恰好是(leaf+1)/2次,把所有點收縮到了一起。
點評:
邊雙連通分量縮點後,原圖變成一顆真正的樹,而樹上各種操作可以和其他知識點結合起來。
這種敏感性要有,比如縮點之後就可以快速求必經邊,必經點之類的。
比較懶,就把老師ppt上的題解粘過來了。
程式碼
#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;
}
B. 逃不掉的路
題目
題目描述
現代社會,路是必不可少的。任意兩個城鎮都有路相連,而且往往不止一條。但有些路連年被各種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;
任意兩個城鎮都有路徑相連;同一條道路不會出現兩次;道路的起終點不會相同;查詢的兩個城市不會相同。
題解
經過分析後,就可以得出方法:
既然是求必須經過的邊,那麼邊雙包含的集合肯定不是必須經過的,這樣我們可以先劃分出邊雙連通分量,進行縮點,這樣就構建了一棵樹,而樹上任意兩點間逃不掉的路的條數(邊權為1)就是他們的距離,求樹上兩點間的距離即可。
所以,綜上所述,這是一道邊雙+LCA的模板題。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+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],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 vc[maxn<<1],Nc[maxn<<1],hc[maxn],lc;
inline void addc(int x,int y)
{
vc[++lc]=y,Nc[lc]=hc[x],hc[x]=lc;
}
int d[maxn],f[maxn][21],t;
queue<int>q;
inline void bfs(int s)
{
q.push(s);
d[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=hc[x];i;i=Nc[i])
{
int y=vc[i];//錯誤原因2:vc打成ver
if (d[y]) continue;
d[y]=d[x]+1;
f[y][0]=x;
for (int j=1;j<=20;++j)//錯誤原因3:++j打成++i
f[y][j]=f[f[y][j-1]][j-1];
q.push(y);
}
}
}
inline int lca(int x,int y)
{
if (d[x]>d[y]) swap(x,y);
for (int i=20;i>=0;--i)
if (d[f[y][i]]>=d[x]) y=f[y][i];//錯誤原因1:未寫‘=’號
if (x==y) return x;
for (int i=20;i>=0;--i)
if (f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.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);
lc=1;
for (int i=2;i<=len;++i)
{
int x=ver[i^1],y=ver[i];
if (c[x]==c[y]) continue;
addc(c[x],c[y]),addc(c[y],c[x]);
}
bfs(1);
int q;read(q);
for (int i=1;i<=q;++i)
{
int x,y;
read(x);read(y);
printf("%d\n",d[c[x]]+d[c[y]]-(d[lca(c[x],c[y])]<<1));
}
return 0;
}
C.HDU 3394 railway 點雙聯通+橋
題目p1691
描述 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個以上的環了。
四.HDU的資料挺水的。
程式碼
簡陋註釋,可能有錯,歡迎大家指出!!!!!!
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int maxm=1e4+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 bridge,vDCC;
int ver[maxn<<1],Next[maxn<<1],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=1;j<=cnt;++j)
{
int x=belong[j];
for (int i=head[x];i;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],low1[maxm],id=0;
void tarjan(int x,int inedge)//點雙連通縮點方法:清空路徑,列舉ver,Next陣列中儲存的路徑,建立雙向邊
{
low[x]=low1[x]=dfn[x]=++id;
Stack[++top]=x;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i];
if (!dfn[y])//y結點還未訪問過
{
tarjan(y,i);//訪問y結點
low[x]=min(low[x],low[y]);//更新low
low1[x]=min(low1[x],low1[y]);//錯誤原因,割邊與割點的判定是不一樣的
if (low1[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;
instack[k]=1;
} while (k!=y);
belong[++cnt]=x;
instack[x]=1;
GetvDCC();
}
}
else
{
low[x]=min(low[x],dfn[y]);//與有向圖區分,此處else不需要判別y節點是否在棧內
if (i!=(inedge^1)) low1[x]=min(low1[x],dfn[y]);
}
}
}
int main()
{
freopen("way.in","r",stdin);
freopen("way.out","w",stdout);
while (1)
{
int n,m;read(n);read(m);
if (!n && !m) break;
memset(head,0,sizeof(head));
len=1;
while (m--)
{
int x,y;read(x);read(y);
++x,++y;//結點是從零開始的
if (x==y) continue;
add(x,y),add(y,x);//無向圖
}
memset(dfn,0,sizeof(dfn));
bridge = vDCC = id = top = 0;
for (int i=1;i<=n;++i)
if (!dfn[i]) tarjan(i,-1);
printf("%d %d\n",bridge,vDCC);
}
return 0;
}
D.BZOJ 2730: [HNOI2012]礦場搭建
題目
題解
首先我們知道,對於這張圖,我們可以列舉坍塌的是哪個點,對於每個坍塌的點,最多可以將圖分成若干個不連通的塊,這樣每個塊我們可能需要一個出口才能滿足題目的要求,列舉每個坍塌的點顯然是沒有意義的,我們只需要每個圖的若干個割點,這樣除去割點的圖有若干個塊,我們可以求出只與一個割點相連的塊,這些塊必須要一個出口才能滿足題目的要求,每個塊內有塊內個數種選法,然後將所有滿足一個割點相連的塊的點數連乘就行了。
對於每個與一個割點相連的塊必須建出口可以換一種方式理解,我們將每個塊看做一個點,那麼算上割點之後,這張圖就變成了一顆樹,只有葉子節點我們需要建立出口,因為對於非葉子節點我們不論斷掉哪個點我們都有另一種方式相連,這裡的葉子節點就是與一個割點相連的塊。
最後還有個特判,就是對於一個雙連通圖,我們至少需要選取兩個點作為出口,因為如果就選一個,可能該點為坍塌點,這時我們就任選兩個點就行了,方案數為點數x(點數-1)>>1。
程式碼該如何寫?
先tarjan求一下所有的點雙。
然後對於每一個點雙,分類討論:
1、只有一個割點,必須選一個非割點。
2、有>=2個割點,不用選
3、有0個割點,必須選倆。
比較懶,就把老師ppt上的題解粘過來了。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=510;
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 vDCC,ans=1;//錯誤原因:未開long long
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id,root;
bool cut[maxn];
inline void tarjan(int x,int fa)
{
dfn[x]=low[x]=++id;
int tot=0;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i];
if (!dfn[y])
{
tarjan(y,x);
low[x]=min(low[x],low[y]);
if (low[y]>=dfn[x])
{
++tot;
if (x^root || tot>1) cut[x]=1;
}
}
else if (y!=fa)
low[x]=min(low[x],dfn[y]);
}
}
int c[maxn],dcc,num,cnt;
inline void dfs(int x)
{
c[x]=dcc;
++cnt;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i];
if (c[y]!=dcc && cut[y]) ++num,c[y]=dcc;
if (!c[y])
dfs(y);
}
}
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
for (int Case=1;;++Case)
{
int n=0,m;
read(m);
if (!m) exit(0);
memset(dfn,0,sizeof(dfn));
memset(c,0,sizeof(c));
memset(cut,0,sizeof(cut));
memset(head,0,sizeof(head));
vDCC=dcc=id=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,0);
for (int i=1;i<=n;++i)
if (!c[i] && !cut[i])
{
++dcc,cnt=num=0;
dfs(i);
if (!num) vDCC+=2,ans*=cnt*(cnt-1)/2;
if (num==1) ++vDCC,ans*=cnt;
}
printf("Case %d: %lld %lld\n",Case,vDCC,ans);
}
return 0;
}
相關文章
- 無向連通圖點雙連通分量
- 無向連通圖邊雙連通分量
- POJ 3694 Network 邊雙連通分量+LCA
- 【筆記/模板】無向圖的雙連通分量筆記
- 「學習筆記」雙連通分量、割點與橋筆記
- 強連通分量
- Tarjan求強連通分量
- 【模板】tarjan 強連通分量縮點
- 強連通分量(Tarjan演算法)演算法
- POJ 1236 Network of Schools 強連通分量
- 有向圖的強連通分量 模版
- Tarjan演算法(強連通分量分解)演算法
- Tarjan 求有向圖的強連通分量
- 圖論——強連通分量(Tarjan演算法)圖論演算法
- day8
- Day8 JSONJSON
- kosaraju 和 tarjan演算法詳解(強連通分量)演算法
- UVA-11504 - Dominos(有向圖的強連通分量)
- Day7 割點、割邊和強連通分量
- 2024-05-05 通達信選股 雙黃連
- 019 通過連結串列學Rust之雙連結串列實現PeekRust
- 直流分量2
- 【演算法學習】tarjan 強連通、點雙、邊雙及其縮點 重磅來襲!!!!演算法
- 016 通過連結串列學習Rust之安全的雙連結串列佈局Rust
- 強連通分量及縮點 演算法解析及例題演算法
- 邊分治維護強連通分量(CF1989F,P5163)
- 尋找圖的強連通分量:tarjan演算法簡單理解演算法
- 強聯通分量tarjan
- 連結串列-雙向連結串列
- UDP雙向通訊UDP
- WCF雙工通訊
- 單雙連結串列
- 雙向連結串列
- 前端開發學習Day8前端
- 20行程式碼實現,使用Tarjan演算法求解強連通分量行程演算法
- 連結串列-雙向通用連結串列
- 025 通過連結串列學Rust之使用棧實現雙端佇列Rust佇列
- 雙向通訊之websocketWeb