HELLO WORLD--一起加油(🍺)!|

kingwzun

园龄:3年6个月粉丝:111关注:0

2022-09-07 20:56阅读: 127评论: 2推荐: 1

Kruskal重构树

定义

首先新建 n 个集合,每个集合恰有一个节点,点权为 0。
每次加边按照边权从小到大加入

每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,
同时将两个集合的根节点分别设为新建点的左儿子和右儿子。
然后我们将两个集合和新建点合并成一个集合。将新建点设为根。

不难发现,在进行 n-1轮之后我们得到了一棵恰有 n个叶子的二叉树,同时每个非叶子节点恰好有两个儿子。这棵树就叫Kruskal 重构树。

例子
image

应用

u->v路径上的最大值最小

Kruskal 重构树上两点的 LCA 的权值
= 最小生成树上两个点之间的简单路径上的最大值
= 原图中两个点之间的所有简单路径上最大边权的最小值

注意:
如果需要原图中两个点之间的所有简单路径上最小边权的最大值,则在跑 Kruskal 的过程中按边权大到小的顺序加边。

从u出发只经过边权不超过x的边能到达的节点

我们只要在边权升序的Kruskal重构树中找到深度最小的,点权不超过x的节点(一般用树上倍增来实现),那么这个节点的子树就是所求的答案。

如何用倍增来找呢?
我们考虑当前我们找到的这个节点为x,然后我们倍增枚举它的祖先,由于是升序排序,所以它祖先的点的点权必然大于等于它的点权,于是,我们倍增的时候,只要判断如果它的祖先的点权,就直接,就好了。

模板代码

模板题

NOIP2013 货车运输

思路:
转化问题: 求两点之间所有路径中,最短的边的最大值。

Kruskal重构树模板题

代码

#include<bits/stdc++.h>//kruskal重构树,求两点之间所有的路径中,最短的边的最大值
using namespace std;
int n,m,q;
int nxt[200010],to[200010],head[50010],w[50010],tot;//重构树的边,w是新树点权
int cnt;
int f[50010];//kruskal算法的并查集
int fa[50010][20],dep[50010];//用于求lca
int vis[50010];
struct orz//用于kruskal算法
{
int x,y,val;
}a[100010];
char buf[1<<15],*fs,*ft;
inline char getc(){return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++;}
inline int read()
{
int x=0,f=1; char ch=getc();
while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getc();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48); ch=getc();}
return x*f;
}
void put(int x)
{
if(x==0){putchar('0');putchar('\n');return;}
if(x<0){putchar('-');x=-x;}
int num=0;char ch[16];
while(x) ch[++num]=x%10+'0',x/=10;
while(num) putchar(ch[num--]);
putchar('\n');
}
inline int my(orz a,orz b)
{
return a.val>b.val;
}
inline int find(int x)
{
int r=x,mid,j=x;
while(r!=f[r]) r=f[r];
while(j!=f[j]) mid=f[j],f[j]=r,j=mid;
return r;
}
inline void add(int xx,int yy)
{
to[++tot]=yy;nxt[tot]=head[xx];head[xx]=tot;
}
void dfs(int x)//预处理lca
{
vis[x]=1;
for(int ct=head[x];ct;ct=nxt[ct])
{
int y=to[ct];
fa[y][0]=x;
for(int i=1;i<=15;++i)
fa[y][i]=fa[fa[y][i-1]][i-1];
dep[y]=dep[x]+1;
dfs(y);
}
}
inline int lca(int x,int y)//求解lca
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=15;i>-1;--i) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=15;i>-1;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
a[i].x=read(),a[i].y=read(),a[i].val=read();
for(int i=1;i<=n;++i)f[i]=i,f[n+i]=n+i;//并查集初始化
cnt=n;sort(a+1,a+m+1,my);//kruskal算法+重构
for(int i=1;i<=m;++i)
{
int fx=find(a[i].x),fy=find(a[i].y);
if(fx!=fy)
f[fx]=f[fy]=++cnt,w[cnt]=a[i].val,add(cnt,fx),add(cnt,fy);
}
for(int i=cnt;i>n;--i)//预处理lca,注意原图可能不联通,所以我们可能构出了一个森林
if(!vis[i])
dep[i]=1,fa[i][0]=i,dfs(i);
q=read();//处理询问
for(int i=1;i<=q;++i)
{
int xx=read(),yy=read();
if(find(xx)!=find(yy)) put(-1);//两点可能不联通
else put(w[lca(xx,yy)]);
}
return 0;
}

【2021ICPC上海站】H-Life is a Game

题意
你本身有一个权值。
每个点有一个权值,到达一个点可以获得这个权值;
每条边也有一个权值,只有你自己当前权值大于等于边权才可以走这条边。

q次询问,每次给出初始点和初始边权,输出可获得的最大边权。
思路
krustal重构树
一个点可以获得自己子树所有点权之和。
子树求和 + 倍增跳就好了
代码

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define int long long
const int N=2e5+10;
vector<tuple<int,int,int> >es;
vector<int> g[N];
int be,k;
int n,m,q;
int fa[N];
int f[N][21],sz[N];
int val[N],cnt;
int find(int x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void krus(){
cnt=n;//---------
for(int i=1;i<=n;i++) fa[i]=i;
sort(es.begin(),es.end());
int u,v,w;
for(int i=0;i<m;i++){
tie(w,u,v)=es[i];
u=find(u),v=find(v);
if(u!=v){
val[++cnt]=w;
fa[cnt]=fa[u]=fa[v]=cnt;
g[u].push_back(cnt);
g[cnt].push_back(u);
g[v].push_back(cnt);
g[cnt].push_back(v);
}
}
}
void dfs(int u,int father){
f[u][0]=father;
for(int i=1;i<=20;i++){
f[u][i]=f[f[u][i-1]][i-1];
}
for(int v: g[u]) {
if(v!=father){
dfs(v,u);
sz[u]+=sz[v];
}
}
}
void solve(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++) cin >> sz[i];
for(int i=0;i<m;i++){
int u,v,w;
cin>>u>>v>>w;
es.emplace_back(w,u,v);
}
krus();
dfs(cnt,0);
val[0]=1e18+7;
while(q--){
cin>>be>>k;
int ans=k+sz[be];
while(be!=cnt){
int x=be;
for(int i=20;i>=0;i--){
if(val[f[be][i]]<=ans)
be=f[be][i];
}
if(x==be) break;
ans=sz[be]+k;
}
cout<<ans<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
while(t--) solve();
return 0;
}

P4197 Peaks

思路:

代码:

原文:https://oi-wiki.org/graph/mst/#kruskal-重构树
原文:https://cmwqf.github.io/2019/03/04/浅谈克鲁斯卡尔重构树/
P4197 Peaks

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16667247.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(127)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起