[BJ 省选] 严格次小生成树 (树上LCA)

[BJWC2010] 严格次小生成树 - 洛谷

核心思路

求最近公共祖先、并维护最大值、次大值

an(i,j)  表示节点i 向上走 2^j 步,的最大值

an1 (i,j) 同理,维护次大值

更新公式

an[u][i] = max(an[u][i-1],an[f[u][i-1]][i-1]);
an1[u][i] = max(max(an1[u][i-1],an1[f[u][i-1]][i-1]),(an[u][i-1] == an[f[u][i-1]][i-1]?0:min(an[u][i-1],an[f[u][i-1]][i-1])));

查询公式

ans = max({ans,(w == an[u][i]?an1[u][i]:an[u][i]),(w == an[v][i]?an1[v][i]:an[v][i])});
升至同一高度

ans = max({ans,(w == an[u][i]?an1[u][i]:an[u][i]),(w == an[v][i]?an1[v][i]:an[v][i])});
同步上升

 

AC 代码

#include<bits/stdc++.h>
const int N = 1e6+9;
#define int long long
using namespace std;
int ans,n,m;
struct edge{
	int u,v,w;
} e[N];
struct ede{
	int v,w;
};
bool cmp(edge a,edge b){
	return a.w < b.w;
}
vector<ede> g[N];
bool pd[N];
int fa[N],f[N][21],dep[N],sz[N],an[N][21],an1[N][21];
int find(int x){
    if (x==fa[x]) return x;
    else return fa[x]=find(fa[x]);
}
void krustral(){
	sort(e+1,e+1+m,cmp);
	for(int i = 1;i <= n;i++)fa[i] = i;
	for(int i = 1;i <= m;i++){
		int u = e[i].u,v = e[i].v,w = e[i].w;
		if(find(u) != find(v)){
			pd[i] = 1;
			g[u].push_back({v,w});
			g[v].push_back({u,w});
			fa[find(u)] = find(v);
			ans+=w;
		}
	}
}
void dfs(int u,int p,int w){
	f[u][0] = p;
	dep[u] = dep[p]+1;
	//cout<<u<<" "<<dep[u]<<" o"<<endl;
	an[u][0] = w,an1[u][0] = 0;
	for(int i = 1;i <= 20;i++){
		f[u][i] = f[f[u][i-1]][i-1];
		an[u][i] = max(an[u][i-1],an[f[u][i-1]][i-1]);
		an1[u][i] = max(max(an1[u][i-1],an1[f[u][i-1]][i-1]),(an[u][i-1] == an[f[u][i-1]][i-1]?0:min(an[u][i-1],an[f[u][i-1]][i-1])));
	}
	for(auto vis:g[u]){
		int v = vis.v;
		if(v == p)continue;
		dfs(v,u,vis.w);
	}
}
int lca(int u,int v,int w){
	if(dep[u] < dep[v])swap(u,v);
	int ans = 0;
	if(u == v)return ans;
	for(int i = 20;i >= 0;i--){
		if(dep[f[u][i]] >= dep[v]){
		//	cout<<"入"<<" "<<u<<" "<<v<<endl; 
			ans = max({ans,an1[u][i],(w == an[u][i]?0:an[u][i])});			
			u = f[u][i];		
		}
	}
	
	if(u == v)return ans;
	//cout<<"二阶段"<<" "<<ans<<endl;
	for(int i = 20;i >= 0;i--){
		if(f[u][i] != 0&&f[u][i] != f[v][i]){
			ans = max({ans,(w == an[u][i]?an1[u][i]:an[u][i]),(w == an[v][i]?an1[v][i]:an[v][i])});
			u = f[u][i],v = f[v][i];
		}
	}
	return max({ans,(w == an[u][0]?an1[u][0]:an[u][0]),(w == an[v][0]?an1[v][0]:an[v][0])});
}
int pt = 1e18+7;
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i = 1;i <= m;i++){
		cin>>e[i].u>>e[i].v>>e[i].w;
	}
	krustral();
	dfs(1,0,0);
//	for(int i = 1;i <= n;i++){
//		cout<<dep[i]<<"ok?"<<endl;
//	}
	//cout<<ans<<endl;
	for(int i = 1;i <= m;i++){
		if(!pd[i]){
			int u = e[i].u,v = e[i].v,w = e[i].w;
		//	cout<<"P"<<u<<" "<<v<<" "<<w<<endl;
			int z = lca(u,v,w);
			
			if(z == 0){
				continue;
			}
			if(ans-z+w > ans){
				//cout<<ans+w-z<<"PP"<<endl;
				pt = min(pt,ans-z+w);
			}
		}
	}
	cout<<pt<<endl;
	return 0;
}
/*
次小生成树难寻,
AC严判更费神。
算法精妙须细究,
方得最优解一尘。
*/

 

  • 22
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值