【学习笔记】kruskal重构树

前言

最近一场div2没开出C2,猛掉104分。
赛后补E,发现自己连E1都没思路,一问才知道是kruskal重构树。
好吧,OI时期欠下的债该还了。

kruskal重构树是什么

  1. 它是一棵 2 n − 1 2n-1 2n1 个点的二叉树。点有点权,下面记作 v a l x val_x valx
  2. 它是一个 大/小根堆,如果是最小生成树构建的就是大根堆,反之,如果是最大生成树构建的就是小根堆。
  3. 对于原图上的每一对点 ( x , y ) (x,y) (x,y),他们之间的最小/大边权 v a l x , y val_{x,y} valx,y,如果是最小生成树那就是最小边权,反之亦然。
  4. 重构树所有叶子都是原图上的点,其他的点都不是原图上的点。
  5. 如果原图不连通,你会得到一个重构树森林。

算法流程

  1. 把边按照边权排序。
  2. 初始化并查集。注意重构树有 2 n − 1 2n-1 2n1 个点,所以要开一个大小为 2 n − 1 2n-1 2n1 的并查集。
  3. r t rt rt 为当前最大的点的编号,初始化 r t = n rt=n rt=n
  4. 和 kruskal 一样,按顺序枚举边,如果两个点属于同一个集合就 continue,令 u = g e t f a t h e r ( x ) ,   v = g e t f a t h e r ( y ) u=getfather(x),\ v=getfather(y) u=getfather(x), v=getfather(y)
  5. r t + + rt++ rt++ f a u = f a v = r t fa_u=fa_v=rt fau=fav=rt v a l r t = w x , y val_{rt}=w_{x,y} valrt=wx,y。也就是新建一个点作为 u 和 v 的父亲,并令它的点权等于边权。

例题

[NOI2018] 归程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

思路

毫无疑问,我们肯定要预处理所有点到 1 号点的最短路。
如果是在最大生成树上跑的话,你会发现根本没法做。
考虑根据海拔建 kruskal 重构树。对于一个点 u,如果 v a l u > p val_u>p valu>p v a l f a u ≤ p val_{fa_u}\leq p valfaup,那么 u u u 子树内的所有点都是可以不花任何代价互相到达的。于是我们需要预处理子树内的点到达 1 号点的最短距离,dfs即可。
对于一个出发点 v v v,我们需要找到最后一个没有被淹没的祖先,用倍增即可。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+7,inf=1e18,mod=998244353,S=21;
vector<vector<int>> e,f;
vector<vector<array<int,2>>> E;
vector<int> val,dis,fa;
int n,m;
int gf(int x)
{
	return x==fa[x]?x:fa[x]=gf(fa[x]);
}
void dij()
{
	priority_queue<array<int,2>,vector<array<int,2>>,greater<array<int,2>>> q;
	q.push({0,1});
	dis.assign(2*n,inf);
	dis[1]=0;
	vector<int> vis(n+1);
	while(!q.empty())
	{
		int u=q.top()[1]; q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(auto [v,w]:E[u])
		{
			if(vis[v]||dis[u]+w>=dis[v])
				continue;
			dis[v]=dis[u]+w;
			q.push({dis[v],v});
		}
	}
}
void dfs(int u,int fa)
{
	f[u][0]=fa;
	for(int j=1; j<S; j++)
	{
		f[u][j]=f[f[u][j-1]][j-1];
	}
	for(auto v:e[u])
	{
		dfs(v,u);
		dis[u]=min(dis[u],dis[v]);
	}
}
int query(int u,int p)
{
	for(int i=S-1; i>=0; i--)
	{
		if(val[f[u][i]]>p)
			u=f[u][i];
	}
	return dis[u];
}
void O_o()
{
	cin>>n>>m;
	e.assign(n*2,vector<int>());
	val.assign(n*2,0);
	vector<array<int,4>> edge;//a,l,u,v
	E.assign(n+1,vector<array<int,2>>());
	for(int i=1; i<=m; i++)
	{
		int u,v,l,a;
		cin>>u>>v>>l>>a;
		E[u].push_back({v,l});
		E[v].push_back({u,l});
		edge.push_back({a,l,u,v});
	}
	dij();
	sort(edge.begin(),edge.end(),greater<>());
	fa.assign(2*n,0);
	for(int i=1; i<=n*2-1; i++) fa[i]=i;
	int rt=n;
	for(auto [a,l,x,y]:edge)
	{
		int u=gf(x),v=gf(y);
		if(u==v) continue;
		rt++;
		fa[u]=rt;
		fa[v]=rt;
		e[rt].push_back(u);
		e[rt].push_back(v);
		val[rt]=a;
	}
	f.assign(2*n,vector<int>(21,0));
	dfs(rt,0);
	int Q,K,S;
	cin>>Q>>K>>S;
	int ans=0;
	while(Q--)
	{
		int v,p;
		cin>>v>>p;
		v=(v+K*ans-1)%n+1;
		p=(p+K*ans)%(S+1);
		ans=query(v,p);
		cout<<ans<<"\n";
	}
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(12);
	int T=1;
	cin>>T;
	while(T--)
	{
		O_o();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值