(严格)次小生成树

(严格)次小生成树

介绍

次小生成树顾名思义就是比最小生成树大一点的生成树,严谨地说就是把一张图所有的生成树都找出来并按照边权和从小到大排序,从头开始第一个比最小生成树边权更大的生成树就是次小生成树

实现

不难想到最小生成树和次小生成树的边应该有很多都是一样的,只有部分边有所不同

那么我们就有这样一种想法,首先找出一个最小生成树,在这棵最小生成树的基础上进行修改

具体地说就是在非最小生成树上边中按照边权从小到大枚举每一条边,把枚举到的边加入最小生成树中,这时最小生成树上会出现一个环,我们把这个环上的最大边断开就可以得到非严格最小生成树了(证明是什么?能吃吗?)

如何在环上找最大值呢?可以发现添加的边所连接的两个节点到他们在最小生成树上的LCA的两条路径就会组成一个环,我们需要的就是这两条路径上的最大边权,寻找边权可以在利用倍增求LCA时顺便维护

此外由于我们需要找的是严格次小生成树,所以如果环上的最大边边权恰好等于我们新加入的边的边权,我们就需要删去环上边权第二大的边,在倍增时也需要同时维护,维护次大边权有点细节,具体的内容代码里有详细注释

Code

板子传送门(Luogu P4180)

#include<bits/stdc++.h>
#define in read()
using namespace std;
typedef long long ll;
#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++)
char B[1<<15],*S=B,*T=B;
inline int read()
{
    char c=getchar();
    int x=0;
    while(c<48)c=getchar();
    while(c>47)x=(x*10)+(c^48),c=getchar();
    return x;
}
inline void mwrite(ll a)
{
    if(a>9)mwrite(a/10);
    putchar((a%10)|48);
}
inline void write(ll a,char c)
{
	mwrite(a);
	putchar(c);
}
#define MAXN 100005
#define MAXM 300005
#define INF 0x7f7f7f7f7f7f7f7f
struct Edge_all//原图边 
{
	int fr,to,w;
	bool isin;
	Edge_all(int Fr=0,int To=0,int W=0):isin(0),fr(Fr),to(To),w(W){}
	bool operator <(const Edge_all& x) const{return w<x.w;}
}edge_all[MAXM];
struct Edge//最小生成树上边 
{
	int nxt,to,w;
}edge[MAXM<<1];
int n,m;
ll origin_ans;
int head[MAXN],cnt;
int fa[MAXN];
int f[MAXN][20],mx[MAXN][20],mx2[MAXN][20],dep[MAXN];
//f[i][j]表示点i的2^j级祖先,mx[i][j]和mx2[i][j]分别表示从点i到它的2^j级祖先这条链上的边权最大值和次大值 
inline void create(int ff,int tt,int ww){edge[++cnt].nxt=head[ff],head[ff]=cnt,edge[cnt].to=tt,edge[cnt].w=ww;}//最小生成树连边 
inline int findf(int x){return x==fa[x]?x:fa[x]=findf(fa[x]);}//Kruskal用的并查集 
inline void init()//初始化 
{
	n=in,m=in;
	for(int i=1,t1,t2,t3;i<=m;++i)
		t1=in,t2=in,t3=in,edge_all[i]=(Edge_all){t1,t2,t3};
	for(int i=1;i<=n;++i) fa[i]=i;
}
inline void kruskal()//最小生成树 
{
	sort(edge_all+1,edge_all+m+1);
	for(int i=1,t1,t2;i<=m;++i)
	{
		t1=findf(edge_all[i].fr),t2=findf(edge_all[i].to);
		if(t1==t2)continue;
		fa[t1]=t2;
		create(edge_all[i].fr,edge_all[i].to,edge_all[i].w);
		create(edge_all[i].to,edge_all[i].fr,edge_all[i].w);
		edge_all[i].isin=1;
		origin_ans+=edge_all[i].w;
	}
}
inline void dfs(int pos)
{
	//递归到这里时从根节点到当前节点的路径上的所有节点信息都已经更新完毕了 
	dep[pos]=dep[f[pos][0]]+1;
	for(int i=1;i<19;++i)
	{
		f[pos][i]=f[f[pos][i-1]][i-1];
		if(mx[pos][i-1]==mx[f[pos][i-1]][i-1])
		{//最大值一样直接继承最大值,次大值取两段中更大的 
			mx[pos][i]=mx[pos][i-1];
			mx2[pos][i]=max(mx2[pos][i-1],mx2[f[pos][i-1]][i-1]);
		}
		else if(mx[pos][i-1]>mx[f[pos][i-1]][i-1])
		{//前半段最大值更大就继承前半段更大值,次大值取前半段的次大值和后半段的最大值中更大的 
			mx[pos][i]=mx[pos][i-1];
			mx2[pos][i]=max(mx2[pos][i-1],mx[f[pos][i-1]][i-1]);
		}
		else if(mx[pos][i-1]<mx[f[pos][i-1]][i-1])
		{//后半段最大值更大就继承后半段更大值,次大值取后半段的次大值和前半段的最大值中更大的 
			mx[pos][i]=mx[f[pos][i-1]][i-1];
			mx2[pos][i]=max(mx[pos][i-1],mx2[f[pos][i-1]][i-1]);
		}
	}
	for(int i=head[pos];i;i=edge[i].nxt)//dfs
	{
		if(edge[i].to==f[pos][0]) continue;
		f[edge[i].to][0]=pos;
		mx[edge[i].to][0]=edge[i].w;
		dfs(edge[i].to);
	}
}
inline int lca(int x,int y)//倍增求lca 
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=18;i>-1;--i)
		if(dep[x]-(1<<i)>=dep[y])
			x=f[x][i];
	if(x==y) return x;
	for(int i=18;i>-1;--i)
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
inline ll solve(int x,int y,int w)
{
	int l=lca(x,y),maxn=0,maxn2=0;
	for(int i=18;i>-1;--i)
	{
		if(dep[f[x][i]]>=dep[l])//还没跳到lca就把答案更新 
		{
			if(maxn==mx[x][i]) maxn2=max(mx2[x][i],maxn2);
			//最大值一样就更新次大值
			if(maxn>mx[x][i]) maxn2=max(mx[x][i],maxn2);
			//最大值无法更新就和次大值比较
			if(maxn<mx[x][i]) maxn2=max(mx2[x][i],maxn),maxn=mx[x][i];
			//最大值能更新就先更新次大值,然后更新最大值
			x=f[x][i]; 
		}
		if(dep[f[y][i]]>=dep[l])
		{
			if(maxn==mx[y][i]) maxn2=max(mx2[y][i],maxn2);
			if(maxn>mx[y][i]) maxn2=max(mx[y][i],maxn2);
			if(maxn<mx[y][i]) maxn2=max(mx2[y][i],maxn),maxn=mx[y][i];
			y=f[y][i]; 
		}
	}
	if(w!=maxn) return origin_ans-maxn+w;//如果最大的边不是添加的新边那删就完了
	if(maxn2) return origin_ans-maxn2+w;//最大的边是添加的边就删次大边
	return INF;//啥也没有就返回正无穷,不统计答案 
}
signed main()
{
	init();
	kruskal();
	dfs(1);
	ll ans=INF;
	for(int i=1;i<=m;++i)
		if(!edge_all[i].isin)
			ans=min(ans,solve(edge_all[i].fr,edge_all[i].to,edge_all[i].w));
	write(ans,'\n');		
    return 0;
}

该文为本人原创,转载请注明出处

博客园传送门

posted @ 2022-03-25 18:54  人形魔芋  阅读(66)  评论(0编辑  收藏  举报