严格次小生成树(倍增+lca+kruskal)

边权和严格大于最小生成树的边权和最小的生成树称为严格次小生成树

根据kruskal的求解原理,每次贪心求局部最优,从而得到全局最优,那么求解次小生成树的过程就是把某一局部最优变成次优,从而得到全局次最优

回顾求解最小生成树的过程,当我们添边组成树时,如果此时树成环,说明有一条边是多余的,我们删除这个环上的一条边也不会影响连通性,所以为使总权值最小,我们贪心地删去环中权值最大的边,而由于kruskal算法每次新添加的边都是树中的最大值,所以当求解次小生成树时只需删除环中次大的边就可以了

 考虑如何找到环中次大边,有这样一条性质:x, y之间的环为 x->lca(x,y)->y;这里要用到倍增求最近公共祖先(lca),于是我们倍增的遍历求lca的过程,就是遍历环的过程,为与倍增遍历配合我们开一个max1[v][j]数组表示节点v到它的第 2^j 个父节点直接路径的边权最大值,转移方程为

    max1[v][j]=max(max1[f[v][j-1]][j-1],max1[v][j-1]); 其中 f[ ][ ]数组为倍增数组、

同时由于所求为严格次小生成树,为避免与最大值相等的情况,还需开个g[][]数组表示次大

转移过程见代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
const ll N=3e5+2;
struct Node{
	int u,v;
    ll w;
	bool flag=0;
	bool operator<(Node s){
		return w<s.w;
	}
}e[N];

inline ll read(){
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int head[100005],idx=1,d[100005],n,m;
int father[100005];
ll g[100005][18],max1[100005][18],sum,ans,lg[20];
int f[100005][18];
vector<pair<ll,ll>>vc[100005];

inline ll find(ll x){
	
	if(father[x]==x) return x;
	return father[x]=find(father[x]);
}
void Kurskal(){
	
	ll fa,fb,q=1;
	sort(e+1,e+1+m);
	for(ll i=1;i<=n;i++) father[i]=i; 
	for(ll i=1;i<=m;i++){
		fa=find(e[i].u);
		fb=find(e[i].v);
		if(fa!=fb){
			q++;
			e[i].flag=1;
			vc[e[i].u].push_back({e[i].v,e[i].w});
			vc[e[i].v].push_back({e[i].u,e[i].w});
			father[fa]=fb; 
			sum+=e[i].w;
		} 
		if(q==n) break;
		
	}
} 
void dfs(ll u){
	ll v,w;
	for(ll i=0;i<vc[u].size();i++){
		v=vc[u][i].first,w=vc[u][i].second;
		if(v!=f[u][0]){
			d[v]=d[u]+1;//记录节点的高度 
			f[v][0]=u;
			max1[v][0]=w;
			for(ll j=1;j<=17;j++){
				if(lg[j]>d[v]) break;
				f[v][j]=f[f[v][j-1]][j-1];//倍增数组 
				max1[v][j]=max(max1[f[v][j-1]][j-1],max1[v][j-1]);//最大值数组
				g[v][j]=max(g[v][j-1],g[f[v][j-1]][j-1]);//次大值数组 
				if(max1[f[v][j-1]][j-1]!=max1[v][j-1])
					g[v][j]=max(g[v][j],min(max1[v][j-1],max1[f[v][j-1]][j-1]));
				
			}
			dfs(v);
		}
	}
	
}
void lca(ll u,ll v,ll w){
	if(d[u]<d[v]) swap(u,v);
	ll t=d[u]-d[v];
	for(ll i=0;lg[i]<=t;i++){
		if(t&lg[i]){
			if(w==max1[u][i])
				ans=max(ans,g[u][i]);
			else
				ans=max(ans,max1[u][i]);
			u=f[u][i];	
		}
	}
	if(u==v) return;
	for(ll i=17;i>=0;i--){
		if(f[u][i]!=f[v][i]){
			if(w==max1[u][i]) ans=max(ans,g[u][i]);
			else ans=max(ans,max1[u][i]);
			if(w==max1[v][i]) ans=max(ans,g[v][i]);
			else ans=max(ans,max1[v][i]);
			u=f[u][i];
			v=f[v][i]; 
		} 
	}
	if(w==max1[u][0]) ans=max(ans,g[u][0]);
	else ans=max(ans,max1[u][0]);
	if(w==max1[v][0]) ans=max(ans,g[v][0]);
	else ans=max(ans,max1[v][0]);
	
	
} 
void work(){
	ll imin=1e9;
	for(ll i=1;i<=m;i++){
		if(!e[i].flag){
			ans=-1;
			lca(e[i].u,e[i].v,e[i].w);
			if(ans!=-1)
				imin=min(imin,e[i].w-ans);
		}
	}
	printf("%lld\n",sum+imin);
}


int main(){
//	1.存边 
//	2.kruskal求最小生成树,建树 
//	3.dfs求倍增数组和路径最大值与次大值数组  
//	4.求lca的过程就是倍增遍历环中路径的过程,从而得到环中路径次大值 
//	更新,求最接近最小生成树边权和的值 
	
	lg[0]=1;
	for(ll i=1;i<=17;i++)
		lg[i]=lg[i-1]*2;
	n=read(),m=read();
	for(ll i=1;i<=m;i++){
		e[i].u=read();
		e[i].v=read();
		e[i].w=read();
	}
	Kurskal();
	dfs(1);
	work();
	
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值