luogu P4180 【模板】严格次小生成树[BJWC2010]

背景:

远古时代的坑了。

题目传送门:

https://www.luogu.org/problemnew/show/P4180

题意:

求一棵树的严格次小生成树的边权和(边权和严格大于最小生成树的生成树)。

思路:

维护最小生成树的一种常见做法就是 L C T LCT LCT
L C T LCT LCT维护最小生成树又分为两种:在线和离线。详见: L C T LCT LCT维护最小生成树
而离线的显然更好打。
考虑 K r u s k a l Kruskal Kruskal的贪心做法。先排序,按照边权升序。若当前两点不连通,则连同它们,同时更新答案。离线的 L C T LCT LCT用类似的思想。

有一个性质(定理什么的):最小生成树与次小生成树(不一定严格)最多只有一条边的权值不等(自己想想,还是不难证的,用一下反证法,再考虑选这条边的是否必要性)。
用这个性质,我们枚举不在最小生成树中的边,看其能否更新此时的次小生成树(因为要严格次小,所以若这两个点对之间所有边长的最大值 = = =当前这一条边的值,那么无论怎么更新得到的还是非严格次小生成树(边权和 = = =最小生成树的和),这时就要用次大值更新;否则用最大值更新),若可以,则更新,并求出差值(最小生成树与严格次小生成树的边权和的差)的最小值。更新差值的比较途径:与当前最小生成树比;或与当前次小生成树比。
最后用最小生成树的边权和加上这个差值即可。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
	struct node1{int d,fa,ma1,ma2,lazy,son[2];} tr[600010];
	struct node2{int x,y,z;} a[600010];
	bool bz[600010];
	int n,m;
	LL ans=0,cha=2147483647;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar())
		if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
	return x*f;
}
bool isroot(int x)
{
	return tr[tr[x].fa].son[0]!=x&&tr[tr[x].fa].son[1]!=x;
}
bool isson(int x)
{
	return x==tr[tr[x].fa].son[1];
}
void change(int x)
{
	if(!x)return;
	swap(tr[x].son[0],tr[x].son[1]);
	tr[x].lazy^=1;
}
void pushup(int x)
{
	int lc=tr[x].son[0],rc=tr[x].son[1];
	tr[x].ma1=tr[x].d;
	tr[x].ma2=max(tr[lc].ma2,tr[rc].ma2);
	if(tr[lc].ma1>tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[x].ma1),tr[x].ma1=tr[lc].ma1;
	else if(tr[lc].ma1<tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[lc].ma1);
	if(tr[rc].ma1>tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[x].ma1),tr[x].ma1=tr[rc].ma1;
	else if(tr[rc].ma1<tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[rc].ma1);
}
void pushdown(int x)
{
	if(!tr[x].lazy) return;
	change(tr[x].son[0]),change(tr[x].son[1]);
	tr[x].lazy=0;
}
void rot(int x)
{
	int w=isson(x),y=tr[x].fa,yy=tr[y].fa;
	tr[y].son[w]=tr[x].son[w^1];
	if(tr[y].son[w]) tr[tr[y].son[w]].fa=y;
	tr[x].fa=yy;
	if(!isroot(y)) tr[yy].son[isson(y)]=x;
	tr[x].son[w^1]=y;tr[y].fa=x;
	pushup(y);
}
int sta[300010];
int top;
void splay(int x)
{
	sta[top=1]=x;
	for(int i=x;!isroot(i);i=tr[i].fa)
		sta[++top]=tr[i].fa;
	while(top) pushdown(sta[top--]);
	for(int y=tr[x].fa;!isroot(x);rot(x),y=tr[x].fa)
		if(!isroot(y)) isson(x)^isson(y)?rot(x):rot(y);
	pushup(x);
}
void access(int x)
{
	for(int y=0;x;y=x,x=tr[x].fa)
		splay(x),tr[x].son[1]=y,pushup(x);
}
int findroot(int x)
{
	access(x);splay(x);
	while(tr[x].son[0]) x=tr[x].son[0];
	//splay(x);
	return x; 
}
void makeroot(int x)
{
	access(x);splay(x);change(x);
}
void link(int x,int y)
{
	makeroot(x);
	if(findroot(y)!=x) tr[x].fa=y;
}
void split(int x,int y)
{
	makeroot(x);access(y);splay(y);
}
node1 query(int x,int y)
{
	split(x,y);
	return tr[y];
}
bool cmp(node2 x,node2 y)
{
	return x.z<y.z;
}
int main()
{
	n=read(),m=read();
	for(int i=0;i<=(n<<1);i++)
		tr[i]=(node1){0,0,0,0,0,0,0};
	for(int i=n+1;i<=n+m;i++)
		a[i].x=read(),a[i].y=read(),a[i].z=read();
	sort(a+n+1,a+n+m+1,cmp);
	for(int i=n+1;i<=n+m;i++)
		if(findroot(a[i].x)!=findroot(a[i].y))
		{
			tr[i].d=a[i].z;
			link(a[i].x,i);link(a[i].y,i);
			ans+=a[i].z;
			bz[i]=true;
		}
	for(int i=n+1;i<=n+m;i++)
	{
		if(bz[i]) continue;
		node1 NOW=query(a[i].x,a[i].y);
		if(a[i].z==NOW.ma1) cha=min(cha,(LL)a[i].z-NOW.ma2); else cha=min(cha,(LL)a[i].z-NOW.ma1);
	}
	printf("%lld",ans+cha);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最小生成树(Minimum Spanning Tree, MST)是一种用于在加权无向连通图中找到最小生成树的算法。最小生成树是指在一个连通无向图中,选取一些边连接所有顶点,且这些边的边权之和最小。 下面给出两种最小生成树算法:Kruskal算法和Prim算法。 1. Kruskal算法 Kruskal算法是一种基于贪心策略的算法,其基本思想是先将图中所有边按照权值从小到大排序,然后依次选取每条边,并检查该边是否会形成环,如果不形成环,则将该边加入最小生成树中。 算法步骤: 1. 对图中所有边按照权值从小到大进行排序。 2. 依次选取每条边,如果该边不会形成环,则将该边加入最小生成树中。 3. 直到最小生成树中包含n-1条边为止。 下面给出一个使用Kruskal算法求解最小生成树的实例: ![kruskal_example](https://cdn.luogu.com.cn/upload/image_hosting/0x6m0q9j.png) 对于以上的图,我们按照边权从小到大进行排序,得到以下的边集合: {(1,2),(1,4),(2,3),(2,4),(3,4),(4,5),(5,6)} 依次选取每条边,并检查该边是否会形成环,最终得到的最小生成树为: ![kruskal_example_mst](https://cdn.luogu.com.cn/upload/image_hosting/3z4jyt0n.png) 2. Prim算法 Prim算法也是一种基于贪心策略的算法,其基本思想是从任意一个顶点开始,依次选择与当前已选顶点集合相邻且权值最小的边,并将其加入最小生成树中。 算法步骤: 1. 从任意一个顶点开始,将该顶点加入已选顶点集合。 2. 依次选择与当前已选顶点集合相邻且权值最小的边,并将其加入最小生成树中。 3. 直到最小生成树中包含n-1条边为止。 下面给出一个使用Prim算法求解最小生成树的实例: ![prim_example](https://cdn.luogu.com.cn/upload/image_hosting/n3x1it0v.png) 假设我们从顶点1开始,按照权值从小到大依次选择与当前已选顶点集合相邻且权值最小的边,并将其加入最小生成树中。得到的最小生成树为: ![prim_example_mst](https://cdn.luogu.com.cn/upload/image_hosting/3cx0d6x3.png)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值