次小生成樹—學習筆記
阿新 • • 發佈:2018-12-30
次小生成樹
分為非嚴格次小生成樹和嚴格次小生成樹
對於前者,若最小生成樹不唯一
則次小生成樹與最小生成樹權值相同
對於後者,則要求次小生成樹權值嚴格大於最小生成樹
接下來的求解方法都將分別討論
這裡是次小生成樹的版題
演算法一:
這一演算法較簡潔易懂,且程式碼量小
但演算法時間複雜度較高,一般不建議用,瞭解思路即可
效率較高的演算法參見演算法二
首先,不難證明次小生成樹的連邊與最小生成樹一定不相同
因此,我們可以列舉每一條在最小生成樹上的邊
在剩下的邊的集合中再求最小生成樹
也就是再對n-1個缺一條邊的圖求最小生成樹
對於嚴格次小生成樹
找出n-1棵樹中找到權值>原最小生成樹且最小的
對非嚴格次小生成樹
找出n-1棵樹中找到權值>=原最小生成樹且最小的
這種思路可以說是很暴力了
程式碼就不貼了,主要講下面的高效演算法
演算法二:
這一演算法程式碼量較大,維護方法不唯一
思路不難,主要在於維護方法的選擇,特別是對於嚴格次小生成樹
演算法時間複雜度是很高效的(也要看維護方法)
對於一棵已經求出的最小生成樹
列舉每一條不在最小生成樹上的邊
並把這條邊加入最小生成樹
設這條邊連線的兩個節點為u和v
這時樹上u到v的路徑會出現迴路
所以我們需要刪掉u到v路徑上的一條邊使其重新變成一顆樹
這時生成樹權值的增量就是新加入的邊權-刪掉的邊權
為了使增量最小,我們要刪去的自然是u到v路徑中權值最大的邊
特別的,若要求的是嚴格次小生成樹
如果發現u到v路徑中權值最大的邊等於加入的邊
就要刪掉u到v路徑上的次大邊權
對於樹上路徑權值的維護選擇有很多
例如樹剖、LCT、倍增等等
這裡用倍增示例
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
using namespace std;
typedef long long lt;
lt read()
{
lt f= 1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=100010;
lt n,m,ans;
struct node{int u,v;lt dis;}edge[maxn*10];
struct node2{lt v,dis,nxt;}E[maxn*10];
int head[maxn],tot;
int fa[maxn];
bool judge[maxn];//判斷是否是最小生成樹的邊
int gra[maxn][18],dep[maxn];
lt fir[maxn][18],sec[maxn][18];
//fir表示區間內最大值,sec表示次大值,和lca一樣的倍增思想
bool cmp(node a,node b){return a.dis<b.dis;}
void add(int u,int v,lt dis)
{
E[++tot].nxt=head[u];
E[tot].v=v;
E[tot].dis=dis;
head[u]=tot;
}
int find(int x)
{
if(x==fa[x]) return x;
else return fa[x]=find(fa[x]);
}
lt kruskal()
{
int num=0; lt len=0;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int u=edge[i].u,v=edge[i].v;lt dis=edge[i].dis;
int fu=find(u),fv=find(v);
if(fu!=fv)
{
fa[fu]=fv; judge[i]=true;
num++; len+=dis;
add(u,v,dis); add(v,u,dis);
if(num==n-1) break;
}
}
return len;
}
void dfs(int u,int pa)
{
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==pa) continue;
dep[v]=dep[u]+1; gra[v][0]=u;
fir[v][0]=E[i].dis; sec[v][0]=-1e9;
dfs(v,u);
}
}
void work()
{
for(int i=1;(1<<i)<=n;i++)
for(int u=1;u<=n;u++)
{
gra[u][i]=gra[ gra[u][i-1] ][i-1];//處理祖先
fir[u][i]=max(fir[u][i-1],fir[ gra[u][i-1] ][i-1]);
sec[u][i]=max(sec[u][i-1],sec[ gra[u][i-1] ][i-1]);
//處理最大和次大值
if(fir[u][i-1]>fir[ gra[u][i-1] ][i-1])
sec[u][i]=max(fir[ gra[u][i-1] ][i-1],sec[u][i]);
else if(fir[u][i-1]<fir[ gra[u][i-1] ][i-1])
sec[u][i]=max(fir[u][i-1],sec[u][i]);
//注意對次大值的判斷
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
int d=dep[u]-dep[v];
for(int i=0;(1<<i)<=d;i++)
if((1<<i)&d) u=gra[u][i];
if(u==v) return u;
for(int i=(int)log(n);i>=0;i--)
{
if(gra[u][i]!=gra[v][i])
u=gra[u][i],v=gra[v][i];
}
return gra[u][0];
}
lt qmax(int u,int v,lt dis)
{
lt tp=-1e9;
for(int i=1;(1<<i)<=n;i++)//倍增思想
{
//只要深度比LCA大,就倍增查詢更新
if(dep[ gra[u][i] ]>=dep[v])
{
if(dis==fir[u][i]) tp=max(tp,sec[u][i]);
else tp=max(tp,fir[u][i]);//發現一樣的邊權要查詢次大值
}
}
return tp;
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
edge[i].u=read(),edge[i].v=read(),edge[i].dis=read();
sort(edge+1,edge+1+m,cmp);
ans=kruskal();//先求最小生成樹
dfs(1,-1);//無根樹轉有根樹
work();//預處理gra,fir和sec
lt add=1e9;
for(int i=1;i<=m;i++)
{
if(judge[i])continue;//若是最小生成樹的邊就跳過
int u=edge[i].u,v=edge[i].v;lt dis=edge[i].dis;
int LCA=lca(u,v);
lt maxu=qmax(u,LCA,dis);
lt maxv=qmax(v,LCA,dis);//分別查詢u、v到他們LCA路徑上的最大值
add=min(add,dis-max(maxu,maxv));//更新最小增量
}
cout<<ans+add;
return 0;
}