次小生成树

input

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

output

包含一行,仅一个数,表示严格次小生成树的边权和。

example

input

5 6 
1 2 1 
1 3 2 
2 4 3 
3 5 4 
3 4 3 
4 5 6 

output

11
思想

1.先用 kruskal 法求最小生成树
2. 然后记录在最小生成树中的每一条路径的最小值及次小值 (不存在= - 1) 可以利用LCA的倍增算法来维护
3. 考虑 i->f[ i ][ j ]与f[ i ][ j ]->f[ f[ i ][ j ] ][ j ]
4. 考虑这两段的最大值相同与不同情况,相同则说明次大值是这两个的次大值的最大值,不同的话,假设(a,b),(c,d)表示两段的(最大,次大),若a>c,显然次大为max(b,c), c>a的情况类似
5. 预处理完,维护沿单链向上跳,记单链的 ( 最大,次大 )为(a,b),当前得到最优值(x,y) , 分三种情况讨论,x与a的大小关系.

#include<bits/stdc++.h>
using namespace std;
int head[100010],cnt,vis[1000010],f[1000010],s[100010][20],d[100010],g[100010][20],h[100010][20];
struct node
{
	int to,w,next;
} a[1000010];
struct line
{
	int u,v,w;
} b[1000010];
bool cmp(line x,line y)
{
	return x.w<y.w;
}
int find(int x)
{
	return f[x]==x?x:f[x]=find(f[x]);
}
void add(int u,int v,int w)
{
	cnt++;
	a[cnt].to=v;
	a[cnt].w=w;
	a[cnt].next=head[u];
	head[u]=cnt;
}
void ddd(int u,int fa)
{
	s[u][0]=fa;
	d[u]=d[fa]+1;
	for(int i=head[u]; i!=-1; i=a[i].next)
	{
		int v=a[i].to;
		if(v==fa) continue;
		g[v][0]=a[i].w;
		h[v][0]=-1;
		ddd(v,u);
	}
}
void sol_1(int &x,int &y,int z,int d,int e,int f)
{
	if(z==e)
	{
		x=z;
		y=max(d,f);
		return;
	}
	if(z>e)
	{
		swap(z,e);
		swap(d,f);
	}
	x=e;
	y=max(z,f);
}
int sol_2(int x,int y,int w)
{
	if(w==x) return w-y;
	return w-x;
}
void sol_3(int &x,int &y,int u,int t)
{
	if(g[u][t]==x) y=max(y,h[u][t]);
	else if(g[u][t]<x) y=max(y,g[u][t]);
	else
	{
		y=(x,h[u][t]);
		x=g[u][t];
	}
}
int sol(int u,int v,int w)
{
	int x=-1,y=-1,dis,t=0;
	if(d[u]<d[v]) swap(u,v);
	dis=d[u]-d[v];
	while(dis>0)
	{
		if(dis&1)
		{
			sol_3(x,y,u,t);
			u=s[u][t];
		}
		t++;
		dis=dis>>1;
	}
	if(u==v) return sol_2(x,y,w);
	for(int i=18; i>=0; i--)
	{
		if(s[u][i]!=s[v][i])
		{
			sol_3(x,y,u,i);
			sol_3(x,y,v,i);
			u=s[u][i];
			v=s[v][i];
		}
	}
	sol_3(x,y,u,0);
	sol_3(x,y,v,0);
	return sol_2(x,y,w);
}
int main()
{
	int i,n,m,tot=0,u,v,uu,vv,j;
	long long ans=0;
	cnt=0;
	scanf("%d%d",&n,&m);
	memset(head,-1,sizeof(head));
	memset(vis,0,sizeof(vis));
	for(i=1; i<=m; i++)
		scanf("%d%d%d",&b[i].u,&b[i].v,&b[i].w);
	sort(b+1,b+1+m,cmp);
	for(i=1; i<=n; i++) f[i]=i;
	for(i=1; i<=m; i++)
	{
		u=b[i].u;
		v=b[i].v;
		uu=find(u);
		vv=find(v);
		if(uu==vv) continue;
		f[uu]=vv;
		vis[i]=1;
		add(u,v,b[i].w);
		add(v,u,b[i].w);
		ans+=b[i].w;
		tot++;
		if(tot==n-1) break;
	}
	ddd(1,0);
	for(j=0; j<18; j++)
	{
		for(i=1; i<=n; i++)
		{
			if(s[i][j]==0) s[i][j+1]=0;
			else
			{
				s[i][j+1]=s[s[i][j]][j];
				sol_1(g[i][j+1],h[i][j+1],g[i][j],h[i][j],g[s[i][j]][j],h[s[i][j]][j]);
			}
		}
	}
	u=0x3f3f3f3f;
	for(i=1; i<=m; i++)
		if(!vis[i]) u=min(u,sol(b[i].u,b[i].v,b[i].w));
	printf("%lld",ans+u);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值