[FROM WOJ]#3775 次小生成树

7 篇文章 0 订阅
3 篇文章 0 订阅

#3775 次小生成树

题面
给定一张 N 个点 M 条边的无向图,求无向图的严格次小生成树。
设最小生成树的边权之和为sum,严格次小生成树就是指边权之和大于 sum 的生成树中最小的一个。

输入
第一行包含两个整数 N 和 M,表示无向图的点数与边数;
接下来 M 行,每行三个数 x,y,z,表示点 x 和点y 之间有一条边,边的权值为 z

输出
包含一行,仅一个数,表示严格次小生成树的边权和。
数据保证必定存在严格次小生成树

样例输入
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6

样例输出
11

提示
对于全部数据, 1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 3 × 1 0 5 1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 3 × 1 0 5 , 1≤N≤10^5,1≤M≤3×10^51≤N≤10^5,1≤M≤3×10^5, 1N105,1M3×1051N105,1M3×105数据中无向图无自环,边权值非负且不超过 1 0 9 ​ ​ 10^9 ​​ 109

SOL
对于一颗最小生成树,我们可以枚举树中的每条非树边并用这条边去尝试做出一些替换,但是光靠枚举效率很低,于是想到了LCA。
用LCA维护每条非树边两端点的LCA路径上的最大值和次大值(因为对于每一条非树边的两个端点,它的两个端点的树边上的边权最大值肯定小于等于这条非树边的权值,所以只需要考虑最大值和次大值)然后每次用非树边尝试替换,求出严格次小生成树。

代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define M 300005
using namespace std;
inline int rd(){
	int register data=0,w=1;static char ch=0;
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')w=-1,ch=getchar();
	while(isdigit(ch))data=(data<<1)+(data<<3)+ch-'0',ch=getchar();
	return data*w;
}
inline void write(int x){if(x>9)write(x/10);putchar(x%10+'0');}
int n,m,sum,minn=0x7f7f7f7f7f;
int fa[N],f[N][20],fir[N][20],sec[N][20],dep[N];
bool used[M];
inline int find(int x){return fa[x]==x? x:fa[x]=find(fa[x]);}
struct node{int u,v,w,nxt;}e[M<<1],E[M];
int cnt,first[N];
inline void add(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;}
inline bool cmp(node a,node b){return a.w<b.w;}
inline void dfs(int u){
	for(int register i=1;(1<<i)<=dep[u];i++){
		f[u][i]=f[f[u][i-1]][i-1];
		fir[u][i]=max(fir[f[u][i-1]][i-1],fir[u][i-1]);
		if(fir[u][i-1]!=fir[f[u][i-1]][i-1])sec[u][i]=min(fir[u][i-1],fir[f[u][i-1]][i-1]);
		sec[u][i]=max(sec[u][i],max(sec[u][i-1],sec[f[u][i-1]][i-1]));
	}
	for(int register i=first[u];i;i=e[i].nxt){
		int register v=e[i].v;
		if(dep[v])continue;
		dep[v]=dep[u]+1;
		fir[v][0]=e[i].w;
		f[v][0]=u;
		dfs(v);
	} 
}
typedef pair<int,int> T;
#define mp make_pair
inline T lca(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	int register gap=dep[a]-dep[b],firs=0,seco=0;
	for(int register i=0;(1<<i)<=gap;i++)
		if(gap&(1<<i)){
			if(firs!=fir[a][i])seco=max(seco,min(firs,fir[a][i]));
			seco=max(seco,sec[a][i]);
			firs=max(firs,fir[a][i]);
			a=f[a][i];
		}
	if(a==b)return mp(firs,seco);
	for(int register i=18;i>=0;i--){
		if(f[a][i]!=f[b][i]){
			if(firs!=fir[a][i])seco=max(seco,min(firs,fir[a][i]));
			seco=max(seco,sec[a][i]);
			firs=max(firs,fir[a][i]);
			if(firs!=fir[b][i])seco=max(seco,min(firs,fir[b][i]));
			seco=max(seco,sec[b][i]);
			firs=max(firs,fir[b][i]);
			a=f[a][i];b=f[b][i];			
		}
	}
	seco=max(seco,max(sec[b][0],sec[a][0]));
	if(firs!=fir[a][0])seco=max(seco,min(firs,fir[a][0]));
	if(firs!=fir[b][0])seco=max(seco,min(firs,fir[b][0]));
	firs=max(firs,max(fir[a][0],fir[b][0]));
	return mp(firs,seco);
}
signed main(){
	n=rd();m=rd();
	for(int register i=1;i<=n;i++)fa[i]=i;
	for(int register i=1;i<=m;i++){E[i].u=rd();E[i].v=rd();E[i].w=rd();}
	sort(E+1,E+m+1,cmp);
	for(int register i=1;i<=m;i++){
		int register u=E[i].u,v=E[i].v,w=E[i].w;
		int register fu=find(u),fv=find(v);
		if(fu!=fv){
			sum+=w;
			add(u,v,w);add(v,u,w);
			fa[fu]=fv;used[i]=1;
		}
	}
	dep[1]=1;dfs(1);
	for(int register i=1;i<=m;i++){
		if(used[i])continue;
		int register u=E[i].u,v=E[i].v,w=E[i].w;
		T tmp=lca(u,v);
		if(tmp.first!=w)minn=min(minn,w-tmp.first);
		else minn=min(minn,w-tmp.second);
	}
	write(sum+minn);return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值