Kruskal 重构树
开坑王!缘由是模拟赛 T4 想到了类似的结构,但是死活想不起来叫什么算法。
对于某些图上路径在线最值问题,我们很难处理,不如考虑离线做法,看一个例题:
P4197
给定一个
个点, 条边的图,每个点有点权 ,每条边有权 , 组询问,每次给定 ,表示求从 开始,经过权值不超过 能到达的第 大点权。
这个东西离线还是好做的,将询问按照
现在我们重新考虑在线做法,考虑在离线做法基础上优化。
我们每次加边的过程,其实就是 Krukal 最小生成树的过程,这个过程可以用一个二叉树形结构表示出来对吧,每次将两个点集合并,生成一个新的点集,我们将新点重编号,将原点集的父亲设为新点,则新点会生成一棵树,这棵树就是 Kruskal 重构树。
给几个图吧,这是原图:
这是 Kruskal 重构树(标黑点表示整棵重构树的根):
至此,我们已经发明了这个算法,来分析一下性质,我们将每次合并两点集所用的边的边权赋给新建点,称其为这个点的点权
性质
:对于一条从根到叶子的路径, 一定单调不增,整体形成一个大根堆。
由于我们是生成的是最小生成树,保证的合并时的边权从小到大,所以这条性质是显然的。
性质
:对于两点 , 一定他们路径上边权最大值的最小值。
同理啊,在合并最小生成树时第一次使他们联通的边就是重构树上的
性质
:重构树是一棵二叉树。
显然,也方便了线段树合并,每次把儿子合并上来就好了,当然如果静态也可以树上启发式合并。
性质
:重构树的点数不超过 。
并查集大小开
具体代码:
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;
}
再额外写一个,假设我们要在重构树上查询第一个
倍增当然可以,但是写一下树剖做法,我们每次跳链,当
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;}
好了,这下就说完了,来看看这个题的具体做法。
上面那个其实是弱化版,也可以看强化版。
我们现在要处理的每次询问
线段树合并:
#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
填坑!
给定一张图,每边有长度
和权值 , 次询问,每次询问以 为出发点,经过 的边,能到达的点到 的距离最短是多少,强制在线。
到
P9638
一张图,每条边有权,支持三个操作:
- 将
修改为 - 经过
的边最多到达多少点 - 修改某条边权,但不改变边权排名。
板题吧,不改变排名就不需要改变重构树的形态,每次每次查到祖先之后查询子树叶子个数就好了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话