• Kruskal 重构树相关


    介绍

    一种在省选题中较平凡的 ( ext{trick})。由建树和重构两部分组成,但最常见的写法是不建树而直接重构,这样能够降低代码复杂度。对于一类只关注连通性的题目,可以起到奇效。对于另一类既关注连通性,又关注最短路径等其他信息的题目,不那么套路,或者根本不可做。

    概述

    ( ext{Kruskal}) 求出一条树边 ((u,v)) 时,将 (u,v) 所在的并查集合并到新建的节点 (x) 上,在 (mathrm{find(u)})(x) 间连一条边,在 (mathrm{find(v)})(x) 间连一条边。并将新建节点 (x) 的点权赋为 ((u,v)) 的权值。

    原理

    • 基于 (mathrm{Kruskal}) 求最小 (/) 大生成树的做法。

    • 重构树的连通性与原树相同。

    性质

    • (mathrm{Kruskal}) 重构树是一棵二叉树。(性质 (1)

    • (mathrm{Kruskal}) 重构树上的点权满足堆的性质。 (性质 (2)

    • 仅有 (mathrm{Kruskal}) 重构树的叶子节点为原生成树上的节点,叶子节点没有点权。(性质 (3)

    • 若将最小生成树重构,从原树上一点 (x) 出发仅经过权值不超过 (k) 的边能够到达的点 (z) 一定在 (mathrm{Kruskal}) 重构树上 (y) 的子树内,其中 (y)(x) 在重构树上向上只经过点权不超过 (k) 的点,能够到达的深度最小的祖先节点。最大生成树同理。(性质 (4)

    对性质 (2) 的证明:按照边权单调加入生成树,加入越晚的一定深度越浅,因此对于任意 (x) 的父亲节点 (fa) 与节点 (x) 的点权一定存在单调性,即满足堆的性质。

    对性质 (4) 的证明:性质 (4) 基于性质 (2)。并且重构树的连通性与原树一致,于是证毕(

    为啥不证性质 (1,3) 呢?因为太显然了(

    例题

    [BZOJ 3551] Peaks加强版

    题意:(n) 个点 (m) 条无向边的图,(Q) 组询问,询问从给定点 (v_i) 出发只经过边权 (leq x_i) 的边能够到达的点中,具有第 (k) 大点权的点,要求强制在线。

    根据原图的最小生成树建出 (mathrm{Kruskal}) 重构树后,用倍增跳到重构树上深度最小的点权 (leq x_i) 的点 (y),然后主席树维护 (y) 的子树内第 (k) 大即可。

    注意重构树上点权为原生成树上边权,与题目给出点权不同。

    #include<cstdio>
    #include<algorithm>
    int num=0,cnt=0,t=0;
    struct edge {int x,y,w;} e[500005];
    int c[200005],val[200005],b[200005];
    int f[200005][25],g[200005][25];
    int h[200005],to[400005],ver[400005];
    int rt[200005],sonL[30000005],sonR[30000005],sum[30000005];
    int fa[200005],bel[200005],dfn[200005],rev[200005],size[200005];
    inline int read() {
    	register int x=0,f=1;register char s=getchar();
    	while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
    	while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    	return x*f;
    }
    inline void add(int x,int y) {to[++cnt]=y;ver[cnt]=h[x];h[x]=cnt;}
    inline int max(const int &x,const int &y) {return x>y? x:y;}
    inline bool cmp(const edge &x,const edge &y) {return x.w<y.w;}
    inline int find(int x) {return x==fa[x]? x:fa[x]=find(fa[x]);}
    inline void assign(int x,int y) {sonL[x]=sonL[y];sonR[x]=sonR[y];sum[x]=sum[y];}
    inline void prework(int x) {
    	for(register int i=1;i<=20;++i) f[x][i]=f[f[x][i-1]][i-1],g[x][i]=max(g[x][i-1],g[f[x][i-1]][i-1]); size[x]=1; dfn[x]=++num; rev[num]=x;
    	for(register int i=h[x];i;i=ver[i]) {int y=to[i]; f[y][0]=x; g[y][0]=val[x]; prework(y); size[x]+=size[y];}
    }
    inline int jump(int u,int lim) {for(register int i=20;i>=0;--i) {if(f[u][i]&&g[u][i]<=lim) u=f[u][i];} return u;}
    inline void change(int &p,int lst,int l,int r,int x) {
    	assign(p=++t,lst); ++sum[p]; if(l==r) return; int mid=l+r>>1;
    	x<=mid? change(sonL[p],sonL[lst],l,mid,x):change(sonR[p],sonR[lst],mid+1,r,x);
    }
    inline int ask(int x,int y,int l,int r,int rk) {
    	if(sum[y]-sum[x]<rk) return -1; if(l==r) return l; int mid=l+r>>1;
    	if(sum[sonR[y]]-sum[sonR[x]]>=rk) return ask(sonR[x],sonR[y],mid+1,r,rk);
    	return ask(sonL[x],sonL[y],l,mid,rk-(sum[sonR[y]]-sum[sonR[x]]));
    }
    int main() {
    	int n=read(),m=read(),Q=read(),tot=n; int ans=0;
    	for(register int i=1;i<=n;++i) b[++b[0]]=c[i]=read();
    	for(register int i=1;i<=2*n-1;++i) fa[i]=i;
    	std::sort(b+1,b+1+b[0]); b[0]=std::unique(b+1,b+1+b[0])-b-1;
    	for(register int i=1;i<=m;++i) {e[i].x=read();e[i].y=read();e[i].w=read();} std::sort(e+1,e+1+m,cmp);
    	for(register int i=1,fx,fy;i<=m;++i) if((fx=find(e[i].x))!=(fy=find(e[i].y))) {val[++tot]=e[i].w; fa[fx]=fa[fy]=tot; add(tot,fx); add(tot,fy);}
    	for(register int i=n+1;i<=tot;++i) if(fa[i]==i) prework(i);
    	for(register int i=1;i<=tot;++i) {int x=std::lower_bound(b+1,b+1+b[0],c[rev[i]])-b;if(c[rev[i]]) change(rt[i],rt[i-1],1,b[0],x); else rt[i]=rt[i-1];}
    	while(Q--) {
    		int v=read(),x=read(),k=read(); if(ans!=-1) v^=ans,x^=ans,k^=ans;
    		int pos=jump(v,x); if(val[pos]>x||pos==v) {printf("-1
    "); ans=-1; continue;} 
    		int res=ask(rt[dfn[pos]-1],rt[dfn[pos]+size[pos]-1],1,b[0],k);
    		if(res!=-1) printf("%d
    ",ans=b[res]); else printf("-1
    "),ans=-1;
    	}
    	return 0;
    }
    

    [NOI2018]归程

    题意:(n) 个点 (m) 条无向边的图,每条边有两个信息 ((l,a)),有 (Q) 个询问, 每次给出出发点 (v_i) 和约束 (p_i)。求从 (v_i) 出发到达 (1) 的最短路径,其中从 (v_i) 出发,(ageq p_i) 的一段连续路径不计路径长度。

    根据原图关于 (a) 的最大生成树建出 (mathrm{Kruskal}) 重构树后,用倍增跳到重构树上深度最小的点权 (geq p_i) 的点 (y),然后维护 (y) 的 子树内的点到达点 (1) 的最短路的最小值即可。最短路直接 (mathrm{dijkstra}) 预处理。

    #include<cstdio>
    #include<queue> 
    #include<functional>
    #include<algorithm>
    typedef long long ll;
    const ll inf=1e15;
    int cnt=0,n;
    struct edge {int x,y,w1,w2;} e[400005];
    ll dis[200005],minn[400005];
    int f[400005][25],g[400005][25];
    int fa[400005],val[400005],vis[200005];
    int h[400005],to[800005],ver[800005],w[800005];
    inline int read() {
    	register int x=0,f=1;register char s=getchar();
    	while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
    	while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
    	return x*f;
    }
    inline int min(const int &x,const int &y) {return x<y? x:y;}
    inline ll min(const ll &x,const ll &y) {return x<y? x:y;} 
    inline int max(const int &x,const int &y) {return x>y? x:y;}
    inline void add(int x,int y,int z=0) {to[++cnt]=y;ver[cnt]=h[x];w[cnt]=z;h[x]=cnt;}
    inline int find(int x) {return x==fa[x]? x:fa[x]=find(fa[x]);}
    inline bool cmp(const edge &x,const edge &y) {return x.w2>y.w2;}
    inline void dijkstra() {
    	dis[1]=vis[1]=0; for(register int i=2;i<=n;++i) dis[i]=inf,vis[i]=0; 
    	std::priority_queue<std::pair<ll,int> > Q; Q.push(std::make_pair(0,1));
    	while(Q.size()) {
    		int x=Q.top().second; Q.pop(); if(vis[x]) continue; vis[x]=1;
    		for(register int i=h[x],y;i;i=ver[i]) if(dis[y=to[i]]>dis[x]+w[i]) {dis[y]=dis[x]+w[i];Q.push(std::make_pair(-dis[y],y));}
    	}
    }
    inline void prework(int x) {
    	for(register int i=1;i<=20;++i) f[x][i]=f[f[x][i-1]][i-1],g[x][i]=min(g[x][i-1],g[f[x][i-1]][i-1]); if(x<=n) minn[x]=dis[x]; else minn[x]=inf;//,printf("minn=%d %lld
    ",x,minn[x]);
    	for(register int i=h[x],y;i;i=ver[i]) {f[y=to[i]][0]=x; g[y][0]=val[x]; prework(y); minn[x]=min(minn[x],minn[y]);} //minn[x]-=c[x]; printf("minn=%d %lld
    ",x,minn[x]);
    }
    inline int jump(int u,int lim) {for(register int i=20;i>=0;--i) if(f[u][i]&&g[u][i]>lim) u=f[u][i]; return u;}
    int main() {
    	int T=read();
    	while(T--) {
    		n=read(); int m=read(),tot=n; ll ans=0;
    		cnt=0; for(register int i=1;i<=n;++i) h[i]=0; 
    		for(register int i=1;i<=m;++i) {
    			e[i].x=read();e[i].y=read();e[i].w1=read();e[i].w2=read();
    			add(e[i].x,e[i].y,e[i].w1); add(e[i].y,e[i].x,e[i].w1);
    		}
    		dijkstra(); std::sort(e+1,e+1+m,cmp); //printf("Over
    ");
    //		for(register int i=1;i<=m;++i) printf("%d %d
    ",e[i].x,e[i].y);
    		cnt=0; for(register int i=1;i<=2*n-1;++i) h[i]=0,fa[i]=i;
    		for(register int i=1,fx,fy;i<=m;++i) if((fx=find(e[i].x))!=(fy=find(e[i].y))) {val[++tot]=e[i].w2; fa[fx]=fa[fy]=tot; add(tot,fx); add(tot,fy);}
    //		for(register int i=1;i<=tot;++i) printf("%d ",find(i)); printf("
    ");
    		for(register int i=n+1;i<=tot;++i) if(fa[i]==i) prework(i);
    		int Q=read(),K=read(),S=read();
    //		for(register int i=1;i<=n;++i) printf("%lld ",dis[i]); printf("
    ");
    	//	for(register int i=1;i<=tot;++i) printf("%lld ",minn[i]); printf("
    "); 
    		while(Q--) {
    			int v=(read()+K*ans-1)%n+1,p=(read()+K*ans)%(S+1),u=jump(v,p);
    			printf("%lld
    ",ans=minn[u]);
    		}
    	}
    		
    	return 0;
    }
    

    [IOI2018]狼人

    这道题实质上求从 (S_i) 出发能够到达的点与从 (E_i) 出发能够到达点是否存在交集。

    没有边权怎么用 (mathrm{Kruskal}) 重构树做呢?赋一个边权就好了(

    具体可以参考这篇题解:https://www.cnblogs.com/tommy0103/p/13831833.html

    CF1253F Cheap Robot

    观察一下,设 (dis_u) 为机器人从 (u) 点走到最近的充电站需要的花费。

    显然有以下性质:

    • (xgeq dis_u) 时,有 (x'=c-dis_ugeq x),其中 (x') 表示走到最近的充电站后再回到 (u) 点时的电量。

    如果我们将一条边 ((a,b,w)) 加入我们构造出的新图 (G'),当且仅当 (c-dis_a-wgeq dis_b),表示 (a) 可以通过这条边走到 (b) ,并且 (b) 可以到达最近的充电站,根据上述性质,(b) 到达充电站后再返回一定优于在 (a) 时的电量,那么这样的一条边是可以拓展出去,即对答案产生贡献的。

    (c-dis_a-wgeq dis_b) 进行移项,得到 (cgeq dis_a+dis_b+w),即仅当满足 (cgeq dis_a+dis_b+w) 时,(a,b) 间有边 ((a,b,dis_a+dis_b+w)) 。这个问题就被转化成了一个连通性问题。使用 ( ext{Kruskal}) 重构树求询问给出的两点间在新图 (G') 中边的最大值最小的路径即可。

  • 相关阅读:
    Python基础 2----Python 基础语法
    安卓开发35:一些安卓设计经验总结
    HDU 4707 Pet(DFS(深度优先搜索)+BFS(广度优先搜索))
    对象数组
    Mac与Window之间的共享文件
    实用数据结构总结之二叉树遍历
    csdn的登录框好难看
    图像切割性能评价
    基于Solr的HBase实时查询方案
    图片的缩放源码与使用
  • 原文地址:https://www.cnblogs.com/tommy0103/p/13831666.html
Copyright © 2020-2023  润新知