Kruskal 重构树学习笔记

news/2024/9/20 13:14:03/文章来源:https://www.cnblogs.com/Ishar-zdl/p/17982471

Kruskal 想必大家都不陌生,这是一种求最小生成树的算法。
关于 Kruskal 重构树,就是把一张图转化为一个堆。
具体来说,我们可以处理出来从 \(u\)\(v\) 路径中的点权或边权的极值。
image
比如上面这张图(前为编号,[ ]内为点权),我们可以将它重构为小顶堆,如下
image
请注意,这棵树有着严格的方向,节点11为他的根节点。
那么如何去实现这个过程呢。
我们仍然是将边排序,然后扫描,用并查集判断是否合法,和正常的 \(Kruskal\) 最小生成树算法一样。
如果当前边 \((u,v)\) 合法,我们需要找到 \(u\) 的父亲 \(fu\)\(v\) 的父亲 \(fv\),然后新建一个节点 \(tot\) 点权为 \(w_tot=min(w_fu,w_fv)\)。新建两条边 \(tot\to fu\)\(tot\to fv\),当合法的边数达到 \(2\cdot n-2\) 条时,算法结束。
显然最后会构成一颗二叉树,且满足 \(w_i\le w_x(i\in siz_x)\)。这棵树的所有叶子结点都是真实点,且任意两点之间路径上的最小节点权值为这两个点的 \(lca\) 的权值。
那么 \(Kruskal\) 重构树能够解决什么样的问题呢?
P1967 [NOIP2013 提高组] 货车运输
这道题要求两点之间路线中边权的最小值最大,我们可以重构出一个小顶堆,然后查询 \(u\)\(v\)\(lca\) 的权值是否合法即可。
\(lca\) 直接倍增即可。
代码如下

#include<bits/stdc++.h>
#define endl '\n'
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48);return x*f;
}
const int N=1e5+10;
int n,m,q,fa[N],st[20][N],head[N],cnt,dfn_cnt,dfn[N],f[N],dep[N],w[N];
bool vis[N];
struct EDGE{int u,v,w;}ed[N];
struct edge{int v,nex;}e[N<<1];
inline void add(int u,int v){e[++cnt]={v,head[u]},head[u]=cnt;}
inline bool cmp(EDGE a,EDGE b){return a.w>b.w;}
inline int get(int x,int y){return dep[x]<dep[y]?x:y;}
inline void dfs(int x,int fa){vis[x]=1;st[0][dfn[x]=++dfn_cnt]=x;f[x]=fa;dep[x]=dep[fa]+1;for(int i=head[x];i;i=e[i].nex)dfs(e[i].v,x);
}
inline int LCA(int u,int v){if(u==v)return u;if((u=dfn[u])>(v=dfn[v]))std::swap(u,v);int d=std::__lg(v-u++);   return f[get(st[d][u],st[d][v-(1<<d)+1])];
}
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void Kruskal(){for(int i=1;i<=2*n;++i)fa[i]=i;std::stable_sort(ed+1,ed+m+1,cmp);int _new=n;for(int i=1;i<=m&&cnt<=2*n-2;++i){int u=find(ed[i].u),v=find(ed[i].v);if(u==v)continue;++_new;add(_new,u),add(_new,v);fa[u]=fa[v]=_new;w[_new]=ed[i].w;}for(int i=_new;i;--i)if(!vis[i])dfs(i,0);n*=2;for(int i=1;i<=std::__lg(n);++i)for(int j=1;j+(1<<i)-1<=n;++j)st[i][j]=get(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
inline void work(int u,int v){if(find(u)!=find(v)){std::cout<<-1<<'\n';return;}std::cout<<w[LCA(u,v)]<<'\n';
}
int main(){// freopen("in.in","r",stdin),freopen("out.out","w",stdout);std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);n=read(),m=read();for(int i=1;i<=m;++i)ed[i].u=read(),ed[i].v=read(),ed[i].w=read();Kruskal();q=read();for(int i=1,u,v;i<=q;++i)u=read(),v=read(),work(u,v);
}

其中 Kruskal 重构树(板子)的部分是

inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void Kruskal(){for(int i=1;i<=2*n;++i)fa[i]=i;std::stable_sort(ed+1,ed+m+1,cmp);int _new=n;for(int i=1;i<=m&&cnt<=2*n-2;++i){int u=find(ed[i].u),v=find(ed[i].v);if(u==v)continue;++_new;add(_new,u),add(_new,v);fa[u]=fa[v]=_new;w[_new]=ed[i].w;}for(int i=_new;i;--i)if(!vis[i])dfs(i,0);n*=2;for(int i=1;i<=std::__lg(n);++i)for(int j=1;j+(1<<i)-1<=n;++j)st[i][j]=get(st[i-1][j],st[i-1][j+(1<<i-1)]);
}

这是另一道题 P4899 [IOI2018] werewolf 狼人
思路:从起点找到所有可以到达的点权大于等于 \(L\) 的节点,为集合 \(A\),然后从终点找到所有可以到达的点权小于等于 \(R\) 的点,为集合 \(B\)。如果 \(A\bigcap B\ne \varnothing\),则存在这样的路径,否则不存在。
考虑 kruskal 重构树,构建一个小顶堆(人走),然后从起点往上跳到最后一个权值大于等于 \(L\) 的节点,此时这个点的子树中就全是权值小于等于 \(L\) 的点。大顶堆(furry走)同理。
两个堆分别建一个 dfn 序列 \(a\)\(b\),此时就相当于询问序列 \(a\)\(l_a\)\(r_a\) 范围内的元素有没有在序列 \(b\)\(l_b\)\(r_b\) 的范围内出现。(二维数点),直接上离线树状数组或者主席树即可。

#include<bits/stdc++.h>
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;
}
const int N=4e5+10;
int n,m,q;
struct Edge{int u,v,w;}ed[N];
inline bool cmp_per(Edge a,Edge b){return a.w>b.w;}
inline bool cmp_wolf(Edge a,Edge b){return a.w<b.w;}
struct Ktree{int dfn[N],fa[N],st[25][N],head[N],val[N],dfn_cnt=0,e_cnt=0,lf[N],rf[N];struct edge{int v,nex;}e[N];inline void add(int u,int v){e[++e_cnt]={v,head[u]};head[u]=e_cnt;}inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}inline void dfs(int x){lf[x]=++dfn_cnt;dfn[dfn_cnt]=x;for(int i=1;i<=20;++i)st[i][x]=st[i-1][st[i-1][x]];for(int i=head[x];i;i=e[i].nex)dfs(e[i].v);rf[x]=dfn_cnt;}inline void build(int type){for(int i=1;i<=2*n-1;++i)fa[i]=i;for(int i=1;i<=m;++i)ed[i].w=type?std::min(ed[i].u,ed[i].v):std::max(ed[i].u,ed[i].v);if(type)std::stable_sort(ed+1,ed+m+1,cmp_per);else std::stable_sort(ed+1,ed+m+1,cmp_wolf);int node_tot=n+1;for(int i=1;i<=m&&node_tot<=2*n-1;++i){int u=ed[i].u,v=ed[i].v,w=ed[i].w;int fu=find(u),fv=find(v);if(fu!=fv){val[node_tot]=w;add(node_tot,fu),add(node_tot,fv);st[0][fu]=st[0][fv]=fa[fu]=fa[fv]=node_tot;node_tot++;}}dfs(node_tot-1);}inline int get(int x,int v,int type){for(int i=20;i>=0;--i){if(type&&st[i][x]&&val[st[i][x]]>=v)x=st[i][x];if(!type&&st[i][x]&&val[st[i][x]]<=v)x=st[i][x];}return x;}
}Per,Wolf;
int root[N],cnt;
struct Tree{int siz,ls,rs;}t[N<<5];
inline void update(int p){t[p].siz=t[t[p].ls].siz+t[t[p].rs].siz;}
inline void insert(int last,int p,int x,int l,int r){if(l==r){t[p].siz=t[last].siz+1;return;}int mid=(l+r)>>1;t[p]=t[last];if(x<=mid)insert(t[last].ls,t[p].ls=++cnt,x,l,mid);else insert(t[last].rs,t[p].rs=++cnt,x,mid+1,r);update(p);
}
inline int query(int last,int p,int x,int y,int l,int r){if(l>=x&&r<=y){return t[p].siz-t[last].siz;}int mid=(l+r)>>1,ans=0;if(x<=mid)ans+=query(t[last].ls,t[p].ls,x,y,l,mid);if(y>mid)ans+=query(t[last].rs,t[p].rs,x,y,mid+1,r);return ans;
}
int main(){// freopen("in.in","r",stdin),freopen("out.out","w",stdout);std::ios::sync_with_stdio(false);std::cin.tie(0),std::cout.tie(0);n=read(),m=read(),q=read();int len=2*n-1;for(int i=1;i<=m;++i)ed[i].u=read()+1,ed[i].v=read()+1;Per.build(1),Wolf.build(0);for(int i=1;i<=Wolf.dfn_cnt;++i){root[i]=root[i-1];int x=Wolf.dfn[i];if(x<=n)insert(root[i-1],root[i]=++cnt,Per.lf[x],1,len);}for(int i=1;i<=q;++i){int u=read(),v=read(),l=read(),r=read();u++,v++,l++,r++;int start=Per.get(u,l,1),end=Wolf.get(v,r,0);if(query(root[Wolf.lf[end]-1],root[Wolf.rf[end]],Per.lf[start],Per.rf[start],1,len))std::cout<<1<<'\n';else std::cout<<0<<'\n';}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/770957.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

搭建.Net WebApi并配置Swagger(一)

C#进阶之WebAPI(一) 那么首先第一点:什么是WebAPI?    首先我们了解一下.net framework 的框架构成: 可以看到,WebAPI和mvc同属于B/S模板框架的一种,官方对于WebApi的定义是:WebAPI是一个框架,可以轻松构建HTTP服务,覆盖广泛的客户端,包括浏览器和移动设备,Web…

财务知识-专票和普票的区别

财务知识-专票和普票的区别

可视化自定义表单开源的突出优势表现在哪里?

想要了解这些详细信息,可以关注低代码技术平台、可视化自定义表单开源的相关信息。随着数字化发展潮流的袭来,降本、增效、提质的办公效率得到了很多企业朋友的喜爱与支持。那么,该如何实现这一目标?又如何帮助企业降低开发成本、提升办公效率?想要了解这些详细信息,可以…

『模拟赛』暑假集训CSP提高模拟5

『模拟赛记录』暑假集训CSP提高模拟5Rank 痛失 Rank2A. 简单的序列 签到题。 读入的时候直接处理。比上一个小就从上一位开始除以二,一直到某一位比上一位大或到了第一位为止。Code: #include<bits/stdc++.h> #define fo(x,y,z) for(register int (x)=(y);(x)<=(z);…

OI-Wiki 学习笔记

算法基础 \(\text{Update: 2024 - 07 - 22}\) 复杂度 定义 衡量一个算法的快慢,一定要考虑数据规模的大小。 一般来说,数据规模越大,算法的用时就越长。 而在算法竞赛中,我们衡量一个算法的效率时,最重要的不是看它在某个数据规模下的用时,而是看它的用时随数据规模而增长…

高速收发器:PHY层笔记(一)

笔记: 高速收发器的数据位宽通常有:2,4,8字节等; PCIE喜欢的位宽是1DW = 4 Byte; 这里对高速收发器的设计为4 Byte也就是32位宽; GT中PHY层的字对齐和掩码处理 高速收发器的数据流以SOT开始(和MIPI一样),GT的SOT一般就是K码,标志了开始,其也具有EOT,标志了结束; 但…

Gt收发器控制代码和细节(一)

复位需求: DESCRIPTION在 7 系列 FPGA GTX/GTH/GTP 收发器中,GTTXRESET 和 GTRXRESET 在配置时应默认为低电平,在配置完成后过一段时间再设置为高电平。SOLUTION配置时,必须以顺序模式启动 GTTXRESET 和 GTRXRESET,也就是说 RESETOVRD=1b0 而GTRESETSEL=1b0。 如果 RESETO…

http请求异步通信

1.存储请求上下文(AsyncContextImpl) 》唯一id作为key 2.web生成交换机、routingkey 》唯一id:本机服务routingKey3.发送到其他服务返回时,从第2步中取出routingkey进行返回。

2024 Selenium10个替代品

随着自动化测试需求的不断增长,Selenium作为广泛使用的自动化测试工具,虽然功能强大,但也存在一些限制和挑战。在2024年, 越来越多的替代工具涌现,它们提供了更高效、更易用的解决方案。那么,哪些替代品值得我们关注呢?在自动化测试领域,除了Selenium,还有哪些工具能够…

【C语言】Linux 飞翔的小鸟

【C语言】Linux 飞翔的小鸟 零、环境部署 安装Ncurses库 sudo apt-get install libncurses5-dev壹、编写代码 代码如下: bird.c #include<stdio.h> #include<time.h> #include<stdlib.h> #include<signal.h> #include<curses.h> #include<sy…