次小生成树

重构一遍代码之后终于码出了次小生成树

以此(第一篇博客)纪念。

次小生成树有严格和非严格两种,这边先讲严格,非严格只需要将做严格次小生成树的特判去掉就可以了。

基础

kruskal求最小生成树;
(我用倍增LCA做法所以)最近公共祖先LCA版;
这些理解了便可以解决这模板题了;

具体做法

  1. 先用kruskal跑最小生成树,记录下树边。

  2. 再用dfs建树,建树的同时用 d p [ x ] [ i ] dp[x][i] dp[x][i] 记录下从x开始向上跳 2 j 2^j 2j时的最大边和次大边并保证最大值和次大值不相同(如果是非严格次小生成树只需记录下最大边)。

  3. (以下自己yy出来的,如果不对请谅解) 因为kruskal保证联通的区块都是最小的,所以任意除去最小生成树上的任何一条边,得到的两个联通块都是最小生成树,所以可以枚举非最小生成树树边,用LCA找出该边两端点在树上的路径,并用dp数组维护出最大值和次大值,将枚举到的非最小生成树边与树上路径的最大边替换便可得到用当前边替换得到的最小生成树(如果是严格次小生成树要保证最大值或次大值与枚举到的边不同,非严格则不用);

  4. 枚举所有边,便可得到用所有非树边替换树边得到的最小生成树(kruskal的贪心),然后进行打擂边可求出次小生成树的值了。

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#include<climits>
#define LL long long
using namespace std;
const LL maxn=1e5+5;
const LL maxm=1e5*3+5;

struct line{
	int aside,bside;
	LL value;
}link[maxm];//link数组存储所有边;
int ltop,m;
int ufa[maxn];//并查集
LL MSTsum;

bool isMST[maxm];//记录一条边是否是最小生成树边

vector<int> edge[maxn];//用vector存储树;
int n;

int lfa[maxn][40],lg[maxn],depth[maxn];//lfa是LCA中的倍增数组,lg是预处理出的log2的值(减小常数),depth数组存储每一节点在最小生成树的深度。

struct maxlist{
	LL Max,seMax;
}dp[maxn][40];//dp数组处理出树上路径的最大次大边。

LL ans;

void add(int x,int y,int z){
	link[++ltop].aside=x;
	link[ltop].bside=y;
	link[ltop].value=z;	
}//add加边。

int ask(int x){
	if(ufa[x]==x)return x;
	return ufa[x]=ask(ufa[x]);
}//并查集路径压缩。

void unite(int x,int y){
	ufa[x]=y;
}//合并。

bool cmp(line a,line b){
	return a.value<b.value;
}//sort比较函数。

void kruskal(){
	int sum=0;
	sort(link+1,link+1+m,cmp);
	for(int i=1;i<=m&&sum<n-1;i++){
		int xx=ask(link[i].aside);
		int yy=ask(link[i].bside);
		if(xx!=yy){
			isMST[i]=1;
			unite(xx,yy);
			sum++;
			MSTsum+=link[i].value;
		}
	}
}//kruskal求最小生成树。

void dfs(int x,int f){//dfs建树
	lfa[x][0]=f;
	depth[x]=depth[f]+1;
	for(int i=1;i<=lg[depth[x]];i++){
		lfa[x][i]=lfa[lfa[x][i-1]][i-1];
		
		dp[x][i].Max=max(dp[lfa[x][i-1]][i-1].Max,dp[x][i-1].Max);
		dp[x][i].seMax=max(dp[lfa[x][i-1]][i-1].seMax,dp[x][i-1].seMax);
		if(dp[lfa[x][i-1]][i-1].Max!=dp[x][i-1].Max){//保证最大次大值不同。
			dp[x][i].seMax=max(dp[x][i].seMax,min(dp[lfa[x][i-1]][i-1].Max,dp[x][i-1].Max));
		}//用dp数组记录树上路径的最大次大值。
	}
	
	for(int i=0;i<edge[x].size();i++){
		int p=edge[x][i];
		int to=link[p].aside+link[p].bside-x;
		if(to==f)continue;
		dp[to][0].Max=link[p].value;//当前往上跳1步的最大边权就是自己。
		dp[to][0].seMax=-1;
		dfs(to,x);
	}//继续深搜。
}

int lca(int x,int y){//求LCA。
	if(depth[x]<depth[y])swap(x,y);
	while(depth[x]>depth[y])x=lfa[x][lg[depth[x]-depth[y]]-1];
	if(x==y)return x;
	for(int k=lg[depth[x]];k>=0;k--){
		if(lfa[x][k]!=lfa[y][k]){
			x=lfa[x][k];
			y=lfa[y][k];
		}
	}
	return lfa[x][0];
}
void beat(LL &m1,LL &m2,LL beater){//求取最大次大值时打擂。
	if(beater>m1){
		m2=m1;
		m1=beater;
		return;
	}
	if(beater>m2&&beater!=m1){
		m2=beater;
		return;
	}
	return;
}

LL donate(int x,int y,int c){//倍增求取加入某非树边会最对边权和的贡献。
	int fa=lca(x,y);
	LL m1=-1,m2=-1;
	while(depth[x]>depth[fa]){
		beat(m1,m2,dp[x][lg[depth[x]-depth[fa]]-1].Max);
		x=lfa[x][lg[depth[x]-depth[y]]-1];
	}
	
	while(depth[y]>depth[fa]){
		beat(m1,m2,dp[y][lg[depth[y]-depth[fa]]-1].Max);
		y=lfa[y][lg[depth[y]-depth[fa]]-1];
	}
	if(m1!=c){
		return c-m1;
	}
	if(m2!=-1){
		return c-m2;
	}
	return 0;
}

void init(){//读入。
	int x,y,z;
	scanf("%d%d",&n,&m);
	memset(edge,0,sizeof(edge));
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);

	}
	for(int i=1;i<=n;i++){
		ufa[i]=i;
	}
	for(int i=1;i<=n;i++){
		lg[i]=lg[i-1]+((1<<lg[i-1])==i);
	}
}

void work(){//主函数。
	kruskal();
	for(int i=1;i<=m;i++){
		if(isMST[i]){
			edge[link[i].aside].push_back(i);
			edge[link[i].bside].push_back(i);
		}
	}
	dfs(1,0);
	ans=INT_MAX;
	for(int i=1;i<=m;i++){
		if(!isMST[i]){
			LL t=donate(link[i].aside,link[i].bside,link[i].value);
			if(t!=0)ans=min(ans,t);
		}	
	}
}

void print(){//输出。
	printf("%lld",MSTsum+ans);
}

int main(){
	init();
	work();
	print();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值