1. 程式人生 > >最小瓶頸路

最小瓶頸路

因此 false its http include 分享 bool sin 查詢

定義:

最小瓶頸路問題是指在一張無向圖中,詢問一個點對$(u,v)$,需要找出從$u$到$v$的一條簡單路徑,使路徑上所有邊中最大值最小。

根據查詢次數不同,最小瓶頸路問題可分為單次查詢和多次查詢。

單次查詢:

例題:Luogu P1396 營救 題目鏈接

題解一:

根據“最大值最小”,不難想到二分答案。

答案肯定處於所有邊中最小值和最大值之間,因此我們二分答案,$check$的時候以二分值為基準進行$BFS$,不經過權值大於二分值的邊,如果能搜到終點,則說明二分值過大;如果不能搜到終點,則說明二分值過小。

代碼:

技術分享
 1 #include<bits/stdc++.h>
 2 using
namespace std; 3 const int maxn=1e4+10,maxm=4e4+10,inf=0x7fffffff; 4 int heade[maxn],ev[maxm],ew[maxm],nexte[maxm]; 5 int isvis[maxn]; 6 int n,m,s,t,tot=0,mid; 7 void add_edge(int u,int v,int w){tot++;ev[tot]=v;ew[tot]=w;nexte[tot]=heade[u];heade[u]=tot;} 8 bool dfs(int ui) 9 { 10 int i,vi,wi;bool
flag=false; 11 isvis[ui]=1;if(ui==t){return true;} 12 for(i=heade[ui];~i;i=nexte[i]) 13 { 14 vi=ev[i];wi=ew[i]; 15 if(isvis[vi]||wi>mid){continue;} 16 flag|=dfs(vi); 17 } 18 return flag; 19 } 20 int main() 21 { 22 int i,j,u,v,w,l,r,ans; 23 cin>>n>>m>>s>>t;l=inf;r=-inf;
24 memset(heade,-1,sizeof(heade)); 25 for(i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);l=min(l,w);r=max(r,w);add_edge(u,v,w);add_edge(v,u,w);} 26 while(l<=r) 27 { 28 mid=(l+r)>>1;memset(isvis,0,sizeof(isvis)); 29 if(dfs(s)){ans=mid;r=mid-1;} 30 else{l=mid+1;} 31 } 32 cout<<ans; 33 return 0; 34 }
View Code

題解二:

利用$Kruskal$的過程。將所有邊升序排序,逐一枚舉每條邊嘗試將邊兩端的點所在集合進行合並,如果合並之後$u$和$v$在同一個集合中,則說明此時$u$到$v$連通。由於邊的枚舉是由小到大,因此可以保證最後一次合並的邊的權值就是答案。

代碼:

多次查詢:

例題:Luogu T15193 【模板】最小瓶頸路 題目鏈接

題目背景

題目描述

給定一張帶權無向圖,每次詢問兩個點,找出兩點間的一條簡單路徑,使路徑中權值最大的邊權值最小。

輸入輸出格式

輸入格式:

第一行為三個數字$n,m,q$,分別表示無向圖節點數,邊數和詢問次數。

第二行至第$m+1$行為三個數字$u,v,w$,表示一條邊。

第$m+2$行至第$m+q+1$行為兩個數字$u,v$,表示一次詢問。

輸出格式:

共$q$行,每行一個數字,表示詢問結果。

輸入輸出樣例

輸入樣例#1:
4 8 4
2 1 6
2 2 4
3 2 6
1 4 5
4 4 2
2 4 6
3 4 10
1 2 9
4 1
3 1
3 4
1 4
輸出樣例#1:
5
6
6
5

說明

對於30%的數據,$n,q\leq 500$

對於60%的數據,$n,q\leq 2\times 10^3,m\leq 10^5$

對於100%的數據,$n,q\leq 10^5,m\leq 2\times 10^5$

註意判斷自環和重邊。

題解:

根據單次查詢中的題解二,可以證明該無向圖最小生成樹中u到v的路徑一定是u到v的最小瓶頸路之一(因為最小瓶頸路很可能有多條)。因此,對這張無向圖預先進行$MST$,然後就變成了對於每個詢問$(u,v)$,回答$u$到$v$的路徑上的權值最大值。這個可以用倍增$LCA$解決。

具體思路是在$ST$表預處理時,維護一個從該節點到其$k$級父親中經過的所有邊的最大權值。在查詢中仍然將$u$和$v$往上跳,同時維護路徑中最大值,到其$LCA$結束。

代碼:

技術分享
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1e5+10,maxm=2e5+10,maxj=17,inf=0x7fffffff;
 4 struct edge{int u,v,w;}e[maxm];
 5 int heade[maxn],ev[maxm],ew[maxm],nexte[maxm];
 6 int father[maxn],dep[maxn];
 7 int fa[maxn][maxj],maxv[maxn][maxj];
 8 int n,m,q,root,tot=0,num=0;
 9 void add_edge(int u,int v,int w){tot++;ev[tot]=v;ew[tot]=w;nexte[tot]=heade[u];heade[u]=tot;}
10 int cmp(edge a,edge b){return a.w<b.w;}
11 int find(int x){return father[x]<0?x:father[x]=find(father[x]);}
12 void union_set(int u,int v,int w)
13 {
14     int x=find(u),y=find(v);
15     if(x==y){return;}
16     if(-father[x]>-father[y]){father[x]+=father[y];father[y]=x;}
17     else{father[y]+=father[x];father[x]=y;}
18     add_edge(u,v,w);add_edge(v,u,w);
19     num++;
20 }
21 void dfs(int ui)
22 {
23     int i,vi,wi;
24     for(i=heade[ui];~i;i=nexte[i])
25     {
26         vi=ev[i];wi=ew[i];if(vi==fa[ui][0]){continue;}
27         fa[vi][0]=ui;maxv[vi][0]=wi;dep[vi]=dep[ui]+1;
28         dfs(vi);
29     }
30 }
31 void init()
32 {
33     int i,j;
34     for(j=1;(1<<j)<=n;j++){for(i=1;i<=n;i++){if(fa[i][j-1]){fa[i][j]=fa[fa[i][j-1]][j-1];maxv[i][j]=max(maxv[i][j-1],maxv[fa[i][j-1]][j-1]);}}}
35 }
36 int query(int u,int v)
37 {
38     int i,j,t,tmp=-inf;
39     if(dep[u]<dep[v]){swap(u,v);}
40     t=(int)(log(dep[u])/log(2));
41     for(j=t;j>=0;j--){if(dep[u]-(1<<j)>=dep[v]){tmp=max(tmp,maxv[u][j]);u=fa[u][j];}}
42     if(u==v){return tmp;}
43     for(j=t;j>=0;j--){if(fa[u][j]&&fa[u][j]!=fa[v][j]){tmp=max(tmp,max(maxv[u][j],maxv[v][j]));u=fa[u][j];v=fa[v][j];}}
44     return max(tmp,max(maxv[u][0],maxv[v][0]));
45 }
46 int main()
47 {
48     int i,j,u,v,w;
49     //freopen("data.in","r",stdin);
50     //freopen("test.out","w",stdout);
51     cin>>n>>m>>q;root=(1+n)>>1;
52     memset(heade,-1,sizeof(heade));memset(father,-1,sizeof(father));
53     for(i=1;i<=m;i++){scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);}
54     sort(e+1,e+m+1,cmp);
55     for(i=1;i<=m;i++){u=e[i].u;v=e[i].v;w=e[i].w;union_set(u,v,w);if(num>=n-1){break;}}
56     dfs(root);init();
57     for(i=1;i<=q;i++)
58     {
59         scanf("%d%d",&u,&v);
60         printf("%d\n",query(u,v));
61     }
62     return 0;
63 }
View Code

最小瓶頸路應用:次小生成樹

具體問題是給定一張無向圖,求出一棵生成樹,其所有邊的權值之和僅次於最小生成樹的權值之和(如果有多個最小生成樹的話次小生成樹就是最小生成樹)。

思路:

先求出這個圖的$MST$,然後枚舉所有不在$MST$中的邊,嘗試將其加入$MST$中,這樣勢必會構成一個環。於是我們需要在這個環中任意斷開一條邊(除了剛剛加入的邊),使其依然成為一棵樹。為了盡量使權值之和最小,我們需要斷開樹上加入的邊的兩端點之間權值最大的邊,這樣就將該問題化歸到了最小瓶頸路問題。

最小瓶頸路