严格次小生成树

严格次小生成树

前言

洛谷最优解rank1,u1s1快是真的快,好写是真的好写,理解也很好理解

简直酸爽,舒服了

非原创,仅作解释

正文

严格次小生成树,顾名思义,权值仅仅小于最小生成树

重点解释这个查询

inline int query(int x,int y,int w)//此时的fa记录这个点能跳到的最顶端 
{
	int res=-inf;
	x=find(x),y=find(y);//直接跳到顶端,忽略跳过的边,因为跳过的边已经用来比较过了
	while(x!=y)
	{
		if(dep[x]<dep[y])swap(x,y);//选取更深的点 
		if(val[x]<w) res=max(res,val[x]);//寻找x,y之间得最大值 
		fa[x]=find(father[x]); //父节点连接父节点的父节点
		x=find(x);//往父节点跳
	}
	return res;//返回值
}

其他的相信带佬们一定能看懂
完整代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
const int maxn=1000010;
const int maxm=3000010;
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,m;
int w[maxn],cnt=0,val[maxn];
int fa[maxn],sum=0,vis[maxn],dep[maxn],head[maxn];
int father[maxn];//记录节点的父节点 
struct node{
	int u,v,w;
}g[maxm];
struct edge{
	int v,next,w;
}e[maxn];
inline int read(){
	char c;int x=0,f=1;
	while(c<'0'|| c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while('0'<=c && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}	
	return x*=f;
}
inline void add(int u,int v,int w){
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
inline int find(int x){
	if(x!=fa[x]) return fa[x]=find(fa[x]);
	return x; 
}
bool cmp(node a,node b){
	return a.w<b.w;
}
inline void kruskal()//求最小生成树 
{
	cnt=0;sort(g+1,g+1+m,cmp);
	for(register int i=1;i<=m;++i)
	{
		int u=find(g[i].u),v=find(g[i].v);
		if(u!=v)
		{
			sum+=g[i].w;
			fa[u]=v;
			vis[i]=1;
			if(++cnt==n-1)break;
		}
	}
}
inline void dfs(int u,int f)//类似于树剖预处理dfs2
{
	for(register int i=head[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==f)continue;
		dep[v]=dep[u]+1;//子节点深度 
		father[v]=u;//记录父节点 
		val[v]=e[i].w;//u到v节点的权值 
		dfs(v,u);
	}
}
inline int query(int x,int y,int w)//此时的fa记录这个点能跳到的最顶端 
{
	int res=-inf;
	x=find(x),y=find(y);//直接跳到顶端,忽略跳过的边,因为跳过的边已经用来比较过了
	while(x!=y)
	{
		if(dep[x]<dep[y])swap(x,y);//选取更深的点 
		if(val[x]<w) res=max(res,val[x]);//寻找x,y之间得最大值 
		fa[x]=find(father[x]); //父节点连接父节点的父节点
		x=find(x);//往父节点跳
	}
	return res;//返回值
}

signed main()
{
	n=read(),m=read();//读入 
	for(register int i=1;i<=n;++i)	fa[i]=i;//初始化并查集 
	for(register int i=1;i<=m;++i)	g[i].u=read(),g[i].v=read(),g[i].w=read();//读入 
	kruskal();
	for(register int i=1;i<=m;++i)
		if(vis[i]){
			int u=g[i].u,v=g[i].v,w=g[i].w;
			add(u,v,w),add(v,u,w);//建立最小生成树 
		}
	dep[1]=1;//以1为根,深度为1 
	dfs(1,0);
	int ans=inf;
	for(register int i=1;i<=n;++i)fa[i]=i;
	for(register int i=1;i<=m;++i)
		if(!vis[i]){
			int w=g[i].w;
			int maxx=query(g[i].u,g[i].v,w);
			int num=sum-maxx+w;
			if(num!=sum) ans=min(ans,num);//记录严格最小生成树的值 
		}
	printf("%lld",ans);
	return 0;
}

求最小生成树的时间复杂度\(O(mlogm)\)
预处理和查询每个边只会被枚举一次\(O(m)\)
时间复杂度\(O(mlogm)\)

posted @ 2021-08-27 14:25  归游  阅读(95)  评论(0编辑  收藏  举报