次小生成树

题目来源

定义:

给定一个带边权的图,把图的所有生成树按照权值哦才能高效到大排序,第二小的成为次小生成树。

定理:

对于一张无向图来说,如果存在最小生成树和严格的次小生成树,那么对于任何一颗最小生成树,都存在一棵严格的次小生成树使得两棵树只有一边不同。

方法

这道题我们有两种思路。

  • 1 、我们可以先求出最小生成树,然后在最小生成树里面枚举最小生成树的每一条边删去然后再假设其他的边。
  • 2、 我们先求出最小生成树,然后我们去枚举非树边,然后将这条边加入树中,此时必然形成了一个环,同时从树中删掉一条边(这条边是环上除了我们加的边最大的一条边)使得最终的图还是一棵树。
    1681353919203.png
    在这里我们主要讨论方法二怎么实现。
    首先我们要利用 K r u s k a l Kruskal Kruskal 求出最小生成树,假设最小生成树的答案为 r e s res res
    然后我们还要求出加边后这个环上除了我们加的边最大的边,借助上图也就是求 5 → 6 5\rightarrow 6 56 路径上的最大边。这个就要借助 L C A LCA LCA 了,具体处理见代码
    遍历所有非树边,维护最小值 a n s = m i n ( a n s , r e s + W − M a x ( V , E ) ) ans=min(ans,res+W-Max(V,E)) ans=min(ans,res+WMax(V,E)) ,最终 a n s ans ans 即是次小生成树。
/*

	by:lwq132lwq

*/
#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0);
#define ll long long
#define re register
#define il inline
#define lb(x) ((x)&(-x))
#define pb push_back
#define fr first
#define sc second
#define PI pair<int,int>
#define rep(i,a,n) for(register int i=a;i<=n;i++)
#define fep(i,a,n) for(register int i=n;i>=a;i--)
using namespace std;
template <class T> void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(ch<'0'||ch>'9'){f|=(ch=='-');ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x=f?-x:x;return ;
}
bool cmp(int x,int y){return x>y;}
int gcd(int a, int b){return b ? gcd(b,a%b):a;}
int max(int a,int b){if(a>b) return a;else return b;}
int min(int a,int b){if(a<b) return a;else return b;}
int qkpow(int x,int y){int ans=1;while(y){if(y&1) ans*=x;y>>=1;x*=x;}return ans;}
const double eps=1e-6;
const int maxn=1e5+10;
const int inf=0x3f3f3f3f;
struct edge{
	int x,y,w;
	bool used;
	bool operator < (const edge &a) const
	{
		return w<a.w;
	}
}E[300010];
int F[100010],f[100010][20],d1[100010][20],d2[100010][20];
int head[300010],d[100010],t,tot,n,m,cnt;
struct note
{
	int to,nex,dis; 
}e[300010];
void add(int x,int y,int z)
{
	e[++tot]={y,head[x],z};head[x]=tot;
}
int find(int x)
{
	if(x==F[x]) return x;
	return F[x]=find(F[x]);
}
ll kruskal()
{
	for(int i=1;i<=n;i++) F[i]=i;
	sort(E+1,E+1+m);
	ll res=0;
	for(int i=1;i<=m;i++)
	{
		int x=find(E[i].x),y=find(E[i].y),w=E[i].w;
		if(x==y) continue;
		F[x]=y;
		res+=w;
		E[i].used=1;
	}
	return res;
}
void build()
{
	for(int i=1;i<=m;i++)
	{
		if(E[i].used)
		{
			int x=E[i].x,y=E[i].y,w=E[i].w;
			add(x,y,w);add(y,x,w);
		}
	}
}
void bfs()
{
	queue<int>q;q.push(1);d[1]=1;
	while(!q.empty())
	{
		int x=q.front();q.pop();
		for(int i=head[x];i;i=e[i].nex)
		{
			int y=e[i].to,z=e[i].dis;
			if(d[y]) continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			d1[y][0]=z,d2[y][0]=-inf;
			for(int k=1;k<=t;k++)
			{
				int anc=f[y][k-1];
				f[y][k]=f[anc][k-1];
				d1[y][k]=d2[y][k]=-inf;
				int dis[4]={d1[y][k - 1], d2[y][k - 1], d1[anc][k - 1], d2[anc][k - 1]};
		
				for(int j=0;j<4;j++)
				{
					int dd=dis[j];
                    if (dd>d1[y][k]) d2[y][k]=d1[y][k],d1[y][k]=dd;
                    else if(dd!=d1[y][k]&&dd>d2[y][k]) d2[y][k]=dd;
				}
			}
			q.push(y);
		}
	}
}
int dis[maxn*2];
int lca(int x,int y,int w)
{
	cnt=0;
	if(d[x]<d[y]) swap(x,y);
	for(int k=t;k>=0;k--)
	if(d[f[x][k]]>=d[y])
	{
		dis[++cnt]=d1[x][k];
		dis[++cnt]=d2[x][k];
		x=f[x][k];
	}
	if(x!=y)
	{
		for(int k=t;k>=0;k--)
		{
			if(f[x][k]!=f[y][k])
			{
				dis[++cnt]=d1[x][k];
				dis[++cnt]=d2[x][k];
				dis[++cnt]=d1[y][k];
				dis[++cnt]=d2[y][k];
				x=f[x][k],y=f[y][k];
			}
		}
		dis[++cnt]=d1[x][0];
		dis[++cnt]=d1[y][0];
		dis[++cnt]=d2[x][0];
		dis[++cnt]=d2[y][0];
	}
	int dis1=-inf,dis2=-inf;
	for(int i=1;i<=cnt;i++)
	{
		int dd=dis[i];
		if(dd>dis1) dis2=dis1,dis1=dd;
		else if(dd!=dis1&&dd>dis2) dis2=dd;
	}
	if(w>dis1) return w-dis1;
	if(w>dis2) return w-dis2;
	return inf;
	
}
int main()
{
	read(n);read(m);	t=(int)(log(n)/log(2))+1;
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		read(x);read(y);read(z);
		E[i]={x,y,z,0};
	}
	ll sum=kruskal();
	build();
	bfs();
	ll res=1e18;
	for(int i=1;i<=m;i++)
	{
		if(!E[i].used)
		{
			int x=E[i].x,y=E[i].y,z=E[i].w;
			res=min(res,sum+lca(x,y,z));
		}		
	}
	cout<<res;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值