Kruskal 重构树

开坑王!缘由是模拟赛 T4 想到了类似的结构,但是死活想不起来叫什么算法。

对于某些图上路径在线最值问题,我们很难处理,不如考虑离线做法,看一个例题:

P4197

给定一个 n 个点,m 条边的图,每个点有点权 ai,每条边有权 wiq 组询问,每次给定 uxk,表示求从 u 开始,经过权值不超过 x 能到达的第 k 大点权。

这个东西离线还是好做的,将询问按照 x 排序,边权按照 wi,开指针扫一遍就好了,每次添加边就是合并两个点集,你可以 vector 乱搞或者线段树合并,查询时线段树二分做到 O(nlogn)

现在我们重新考虑在线做法,考虑在离线做法基础上优化。

我们每次加边的过程,其实就是 Krukal 最小生成树的过程,这个过程可以用一个二叉树形结构表示出来对吧,每次将两个点集合并,生成一个新的点集,我们将新点重编号,将原点集的父亲设为新点,则新点会生成一棵树,这棵树就是 Kruskal 重构树。

给几个图吧,这是原图:

这是 Kruskal 重构树(标黑点表示整棵重构树的根):

至此,我们已经发明了这个算法,来分析一下性质,我们将每次合并两点集所用的边的边权赋给新建点,称其为这个点的点权 bi,认为此处边权从小到大排序,大到小同理:

性质 1:对于一条从根到叶子的路径,bi 一定单调不增,整体形成一个大根堆。

由于我们是生成的是最小生成树,保证的合并时的边权从小到大,所以这条性质是显然的。

性质 2:对于两点 u,vblca(u,v) 一定他们路径上边权最大值的最小值。

同理啊,在合并最小生成树时第一次使他们联通的边就是重构树上的 lca,所以保证了此时他们的最小边权最大值为这个。

性质 3:重构树是一棵二叉树。

显然,也方便了线段树合并,每次把儿子合并上来就好了,当然如果静态也可以树上启发式合并。

性质 4:重构树的点数不超过 2n1

并查集大小开 2n1 就够了。

具体代码:

inline void init_Kruskal()
{
	sort(l+1,l+1+m,cmp);fo(1,i,n) fa[i]=i,upd(rot[i],1,len,a[i]);
	fo(1,i,m)
	{
		ll u=l[i].u,v=l[i].v,w=l[i].w;
		if(Find(u)==Find(v)) continue;
		ll fau=Find(u),fav=Find(v);
		++num;g[num].pb(fau),g[num].pb(fav);_a[num]=w;
		fa[num]=num;fa[fau]=num,fa[fav]=num;
	}
	fo(1,i,num) fa[i]=0;
}

再额外写一个,假设我们要在重构树上查询第一个 x 的祖先怎么做:

倍增当然可以,但是写一下树剖做法,我们每次跳链,当 topux 时,xfatopx,否则在链上二分查找:

inline ll check(ll u,ll x)
{
	if(_a[_rt[u]]<=x) return _rt[u];
	ll last=u;
	while(fa[top[u]]&&_a[top[u]]<=x) last=top[u],u=fa[top[u]];
	if(_a[u]>x) return last;
	ll l=id[top[u]],r=id[u];
	while(l<r)
	{
		ll mid=l+r>>1;
		if(_a[w[mid]]<=x) r=mid;
		else l=mid+1;
	}
	return w[l];
}

倍增怎么这么短,补一下代码:

inline void dfs(ll x,ll fa)
{
	fo(1,j,20) f[x][j]=f[f[x][j-1]][j-1];
	if(x<=n) return _siz[x]=1,void();
	for(ll y:g[x]) if(y!=fa) dfs(y,x),_siz[x]+=_siz[y]; 
}
inline ll check(ll x,ll las){Fo(20,i,0) if(f[x][i]&&_a[f[x][i]]>=las) x=f[x][i];return x;}

好了,这下就说完了,来看看这个题的具体做法。

上面那个其实是弱化版,也可以看强化版

我们现在要处理的每次询问 u 上面 x 的祖先的子树第 k 大,这个做法就很多了,你可以转成静态主席树区间询问,或者线段树合并。

线段树合并:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pr putchar('\n')
#define fi first
#define se second
#define pp putchar(' ')
#define pii pair<ll,ll>
#define pdi pair<ll,ll>
#define mem(aa,bb) memset(aa,bb,sizeof(aa))
#define fo(a,i,b) for(register ll i = a ; i <= b ; ++ i )
#define Fo(a,i,b) for(register ll i = a ; i >= b ; -- i )
#define pb push_back
//#pragma GCC optimize(2)
using namespace std;
typedef int ll;
//typedef long long ll;
//typedef __int128 ll;
typedef double db;
inline void read(ll &opp){ll x=0,t=1;char ch;ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){t=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}opp=x*t;return; }
inline void wr(ll x){if(x<0){putchar('-');x=-x;}if(x>9){wr(x/10);}putchar(x%10+'0');}
const ll N=2e6+5,M=2e4+5,mod=1e9+7;
ll n,m,q,a[N],b[N],len,siz[N],top[N],id[N],fa[N],son[N],w[N],dep[N],cnt,rb[N],num,opt,rot[N],_a[N],_siz[N],_rt[N];
vector<ll> g[N];
struct Line{ll u,v,w;}l[N];
inline bool cmp(Line x,Line y){return x.w<y.w;}
inline ll Find(ll x){return fa[x]==x?x:fa[x]=Find(fa[x]);}

struct SGT{ll l,r,sum;}tree[N<<2];
#define rt tree[root]
#define ls tree[tree[root].l]
#define rs tree[tree[root].r]
inline void pushup(ll root){rt.sum=ls.sum+rs.sum;}
inline void upd(ll &root,ll l,ll r,ll x)
{
	root=++opt;if(l==r) return rt.sum=1,void();
	ll mid=l+r>>1;
	if(x<=mid) upd(rt.l,l,mid,x);
	else upd(rt.r,mid+1,r,x);
	pushup(root);
}
inline void merge(ll &root,ll rt1,ll rt2,ll l,ll r)
{
	if(!rt1||!rt2) return root=rt1+rt2,void();
	root=++opt;
	if(l==r) return rt.sum=tree[rt1].sum+tree[rt2].sum,void();
	ll mid=l+r>>1;
	merge(rt.l,tree[rt1].l,tree[rt2].l,l,mid),
	merge(rt.r,tree[rt1].r,tree[rt2].r,mid+1,r);
	rt.sum=ls.sum+rs.sum;
}
inline ll ask(ll root,ll l,ll r,ll k)
{
	if(rt.sum<k) return -1;if(l==r) return l;
	ll mid=l+r>>1;
	if(ls.sum>=k) return ask(rt.l,l,mid,k);
	else return ask(rt.r,mid+1,r,k-ls.sum);
}

inline void init_Kruskal()
{
	sort(l+1,l+1+m,cmp);fo(1,i,n) fa[i]=i,upd(rot[i],1,len,a[i]);
	fo(1,i,m)
	{
		ll u=l[i].u,v=l[i].v,w=l[i].w;
		if(Find(u)==Find(v)) continue;
		ll fau=Find(u),fav=Find(v);
		++num;g[num].pb(fau),g[num].pb(fav);_a[num]=w;
		merge(rot[num],rot[fau],rot[fav],1,len);
		fa[num]=num;fa[fau]=num,fa[fav]=num;
	}
	fo(1,i,num) fa[i]=0;
}

inline void dfs1(ll x,ll fat,ll depth,ll _rot)
{
	dep[x]=depth,fa[x]=fat,siz[x]=1;_rt[x]=_rot;
	ll _cnt=0;
	for(ll y:g[x])
	{
		if(y==fat) continue;
		_cnt++;dfs1(y,x,depth+1,_rot);siz[x]+=siz[y];_siz[x]+=_siz[y];
		if(siz[y]>siz[son[x]]) son[x]=y;
	}
	if(!_cnt) _siz[x]=1;
}
inline void dfs2(ll x,ll nowtop){top[x]=nowtop,id[x]=++cnt,w[cnt]=x;if(!son[x]) return rb[x]=cnt,void();dfs2(son[x],nowtop);for(ll y:g[x]) if(y!=fa[x]&&y!=son[x]) dfs2(y,y);son[x]=cnt;}

inline ll check(ll u,ll x)
{
	if(_a[_rt[u]]<=x) return _rt[u];
	ll last=u;
	while(fa[top[u]]&&_a[top[u]]<=x) last=top[u],u=fa[top[u]];
	if(_a[u]>x) return last;
	ll l=id[top[u]],r=id[u];
	while(l<r)
	{
		ll mid=l+r>>1;
		if(_a[w[mid]]<=x) r=mid;
		else l=mid+1;
	}
	return w[l];
}
signed main(){
	ll o=1;
	read(n),read(m),read(q);num=n;
	fo(1,i,n) read(a[i]),b[i]=a[i];
	sort(b+1,b+1+n);len=unique(b+1,b+1+n)-b-1;
	fo(1,i,n) a[i]=lower_bound(b+1,b+1+len,a[i])-b;
	fo(1,i,m) read(l[i].u),read(l[i].v),read(l[i].w);
	init_Kruskal();
	Fo(num,i,1) if(!fa[i]) dfs1(i,0,1,i),dfs2(i,i);
	ll lastans=0;
	fo(1,i,q)
	{
		ll u,x,k;read(u),read(x),read(k);
		u=(u^(lastans*o))%n+1,k=(k^(lastans*o))%n+1,x=x^(lastans*o);
		ll root=check(u,x),ans=ask(rot[root],1,len,_siz[root]-k+1);
		if(_siz[root]<k||ans==-1) lastans=0,wr(-1),pr;
		else wr(lastans=b[ans]),pr;
	}
	return 0;
}

主席树:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pr putchar('\n')
#define fi first
#define se second
#define pp putchar(' ')
#define pii pair<ll,ll>
#define pdi pair<ll,ll>
#define mem(aa,bb) memset(aa,bb,sizeof(aa))
#define fo(a,i,b) for(register ll i = a ; i <= b ; ++ i )
#define Fo(a,i,b) for(register ll i = a ; i >= b ; -- i )
#define pb push_back
//#pragma GCC optimize(2)
using namespace std;
typedef int ll;
//typedef long long ll;
//typedef __int128 ll;
typedef double db;
inline void read(ll &opp){ll x=0,t=1;char ch;ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-'){t=-1;}ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}opp=x*t;return; }
inline void wr(ll x){if(x<0){putchar('-');x=-x;}if(x>9){wr(x/10);}putchar(x%10+'0');}
const ll N=2e6+5,M=2e4+5,mod=1e9+7;
ll n,m,q,a[N],b[N],len,siz[N],top[N],id[N],fa[N],son[N],w[N],dep[N],cnt,rb[N],num,opt,rot[N],_a[N],_siz[N],_rt[N];
vector<ll> g[N];
struct Line{ll u,v,w;}l[N];
inline bool cmp(Line x,Line y){return x.w<y.w;}
inline ll Find(ll x){return fa[x]==x?x:fa[x]=Find(fa[x]);}

inline void init_Kruskal()
{
	sort(l+1,l+1+m,cmp);fo(1,i,n) fa[i]=i;
	fo(1,i,m)
	{
		ll u=l[i].u,v=l[i].v,w=l[i].w;
		if(Find(u)==Find(v)) continue;
		ll fau=Find(u),fav=Find(v);
		++num;g[num].pb(fau),g[num].pb(fav);_a[num]=w;
		fa[num]=num;fa[fau]=num,fa[fav]=num;
	}
	fo(1,i,num) fa[i]=0;
}

inline void dfs1(ll x,ll fat,ll depth,ll _rot)
{
	dep[x]=depth,fa[x]=fat,siz[x]=1;_rt[x]=_rot;
	ll _cnt=0;
	for(ll y:g[x])
	{
		if(y==fat) continue;
		_cnt++;dfs1(y,x,depth+1,_rot);siz[x]+=siz[y];_siz[x]+=_siz[y];
		if(siz[y]>siz[son[x]]) son[x]=y;
	}
	if(!_cnt) _siz[x]=1;
}
inline void dfs2(ll x,ll nowtop){top[x]=nowtop,id[x]=++cnt,w[cnt]=x;if(!son[x]) return rb[x]=cnt,void();dfs2(son[x],nowtop);for(ll y:g[x]) if(y!=fa[x]&&y!=son[x]) dfs2(y,y);rb[x]=cnt;}

struct SGT{ll l,r,sum;}tree[N<<5];
inline void upd(ll &root,ll las,ll l,ll r,ll x)
{
	root=++opt;tree[root]=tree[las];tree[root].sum++;if(l==r) return;
	ll mid=l+r>>1;if(x<=mid) upd(tree[root].l,tree[las].l,l,mid,x);else upd(tree[root].r,tree[las].r,mid+1,r,x);
}
inline ll ask(ll rt1,ll rt2,ll l,ll r,ll k)
{
	if(l==r) return l;
	ll mid=l+r>>1,now=tree[tree[rt2].r].sum-tree[tree[rt1].r].sum;
	if(now>=k) return ask(tree[rt1].r,tree[rt2].r,mid+1,r,k);
	else return ask(tree[rt1].l,tree[rt2].l,l,mid,k-now); 
}

inline ll check(ll u,ll x)
{
	if(_a[_rt[u]]<=x) return _rt[u];
	ll last=u;
	while(fa[top[u]]&&_a[top[u]]<=x) last=top[u],u=fa[top[u]];
	if(_a[u]>x) return last;
	ll l=id[top[u]],r=id[u];
	while(l<r)
	{
		ll mid=l+r>>1;
		if(_a[w[mid]]<=x) r=mid;
		else l=mid+1;
	}
	assert(_a[w[l]]<=x&&_a[fa[w[l]]]>x);
	return w[l];
}
signed main(){
	ll o=1;
	read(n),read(m),read(q);num=n;
	fo(1,i,n) read(a[i]),b[i]=a[i];
	sort(b+1,b+1+n);len=unique(b+1,b+1+n)-b-1;
	fo(1,i,n) a[i]=lower_bound(b+1,b+1+len,a[i])-b;
	
	fo(1,i,m) read(l[i].u),read(l[i].v),read(l[i].w);
	init_Kruskal();
	Fo(num,i,1) if(!fa[i]) dfs1(i,0,1,i),dfs2(i,i);
	
	fo(1,i,num){rot[i]=rot[i-1];if(w[i]<=n) upd(rot[i],rot[i-1],1,len,a[w[i]]);} 
	ll lastans=0;
	fo(1,i,q)
	{
		ll u,x,k;read(u),read(x),read(k);
		u=(u^(lastans*o))%n+1,k=(k^(lastans*o))%n+1,x=x^(lastans*o);
		ll root=check(u,x),ans=ask(rot[id[root]-1],rot[rb[root]],1,len,k);
		if(_siz[root]<k) wr(-1),pr,lastans=0;
		else if(ans==-1) wr(-1),pr,lastans=0;
		else wr(lastans=b[ans]),pr;
	}
	return 0;
}

P4768

填坑!

给定一张图,每边有长度 li 和权值 aiQ 次询问,每次询问以 u 为出发点,经过 ai>x 的边,能到达的点到 1 的距离最短是多少,强制在线。

1 的距离显然是先做一边 dij,把最短路当作权值放到点上,然后考虑 Kruskal 重构树,按照 ai 从大到小排序,每次向上询问祖先第一个 >x 的,然后查询子树最短路最小值,这个可以提前预处理,复杂度 O(nlogn)

code

P9638

一张图,每条边有权,支持三个操作:

  1. x 修改为 k
  2. 经过 >x 的边最多到达多少点
  3. 修改某条边权,但不改变边权排名。

板题吧,不改变排名就不需要改变重构树的形态,每次每次查到祖先之后查询子树叶子个数就好了。

code

posted @   Wei_Han  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示