kruskal重构树 (mathcal{O(nlogn)})
学习资料:hwzzyr的博客
定义?:
在kruskal算法的过程中,把最小生成树的边权改为点权而构建的二叉树。
抛开kruskal算法来讲,对原图(注意,不止对树,图也可以)的边集进行排序,然后将边当成节点建树。
性质:
-
是一个二叉堆,根据题目而建的小根堆或大根堆。
-
重构树中,原图的节点<=>叶子节点,其余节点,带权,代表一条边的边权。
-
对于小根堆,重构树两个叶子节点的 (lca) 的权重代表着断开这两个节点 需要的最短边 的最大值,或者说,两个叶子节点能只经过大于等于 (lca) 权重的边相互到达。
-
对于大根堆,重构树两个叶子节点的 (lca) 的权重代表着断开着两个节点 需要的最长边 的最小值,或者说,两个叶子节点能只经过小于等于 (lca) 权重的边相互到达。
构造:
- 对原图的边进行排序
- 如果边的两个端点没有连通,用并查集顺序加边
- 新建节点 (index) (编号从 (n+1) 开始)
- 将两端点归入 (index) 集合
- 让 (index) 与两端点连边,且记录当前 (index) 所对应的边权
(code:)
void buildKT()
{
sort(e+1,e+1+m,[](E p1,E p2)->bool{return p1.w>p2.w;});
for(int i=1,up=n+m;i<=up;i++)pre[i]=i;
num=n;
for(int i=1,fu,fv;i<=m;i++)
{
fu=find(e[i].u);fv=find(e[i].v);
if(fu!=fv)
{
val[++num]=e[i].w;
pre[fu]=pre[fv]=num;
add(num,fu);add(num,fv);
}
}
}
例题:
1,牛客 水灾
题意:
给一个 (n) 个节点 (m) 条带权边的无向连通图,有 (q) 次询问,每次询问图中 (k_i) 个互不相同的点,你可以选择一个数 (x) ,然后将图中所有边权小于等于 (x) 的边删除。求当删除这些边后 (k_i) 个点互不连通时, (x) 的最小值。强制在线。
(1le n,mle5 imes10^5,;1lesum_{i=1}^qk_ile10^6,;1le k_ile n,;1le u,vle n,;1le w_ile 10^9)
题解说:
最优情况可以是删去所有任意被询问两点的 (operatorname{lca})。
由于我们只需要知道这些被删去的点中点权的最大值,所以我们只用知道把被询问点按照 dfs 序排序。
那么每组询问的答案就是排序后所有被询问相邻两点的所有 (operatorname{lca}) 的点权的最大值。
非相邻两个被询问点的 (operatorname{lca}) 一定是某相邻两点的 (operatorname{lca}) 的父亲,它的点权一定不是最大的,所以就不需要查询。
(code:)
//#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
#define reg register
#define ll long long
using namespace std;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int maxn=2e6+5;
const double ep=1e-12;
template<typename T>void read(T&x)
{
char c;
while(!isdigit(c=getchar()));x=c-'0';
while(isdigit(c=getchar()))x=(x<<1)+(x<<3)+c-'0';
}
int pre[maxn];
int find(int x)
{
int r=x,t;
while(r!=pre[r])r=pre[r];
while(r!=x)
t=pre[x],pre[x]=r,x=t;
return r;
}
struct E{
int u,v,w;
}e[maxn];
int n,m;
struct Eg{
int to,next;
}eg[maxn];
int head[maxn],cnt;
inline void add(int u,int v){eg[++cnt]={v,head[u]};head[u]=cnt;}
int num,val[maxn];
void buildKT()
{
sort(e+1,e+1+m,[](E p1,E p2)->bool{return p1.w>p2.w;});
for(int i=1,up=n+m;i<=up;i++)pre[i]=i;
num=n;
for(int i=1,fu,fv;i<=m;i++)
{
fu=find(e[i].u);fv=find(e[i].v);
if(fu!=fv)
{
val[++num]=e[i].w;
pre[fu]=pre[fv]=num;
add(num,fu);add(num,fv);
}
}
}
int depth[maxn<<2],id[maxn],rid[maxn<<2],dfcnt,st[maxn<<2][25];
void dfs(int u,int d)
{
// printf("[%d (%d) %d]
",eg[head[u]].to,u,eg[eg[head[u]].next].to);
id[u]=++dfcnt;rid[dfcnt]=u;depth[dfcnt]=d;
for(reg int i=head[u];i;i=eg[i].next)
{
dfs(eg[i].to,d+1);
rid[++dfcnt]=u;depth[dfcnt]=d;
}
}
int lg[maxn<<2];
void init()
{
lg[0]=-1;
for(reg int i=1;i<=dfcnt;i++)lg[i]=lg[i>>1]+1;
for(reg int i=1;i<=dfcnt;i++)st[i][0]=i;
for(reg int j=1;(1<<j)<=dfcnt;j++)
for(reg int i=1;i+(1<<j)-1<=dfcnt;i++)
st[i][j]=depth[st[i][j-1]]<depth[st[i+(1<<j-1)][j-1]]?
st[i][j-1]:st[i+(1<<j-1)][j-1];
}
inline int lca(int u,int v)
{
if(id[u]>id[v])swap(u,v);
int s=id[u],t=id[v],len=lg[t-s+1];
return depth[st[s][len]]<depth[st[t-(1<<len)+1][len]]?
rid[st[s][len]]:rid[st[t-(1<<len)+1][len]];
}
int a[maxn];
int main()
{
int q,u,v,w,k,ans=0;
read(n);read(m);read(q);
for(int i=1;i<=m;i++)
{
read(u);read(v);read(w);
e[i]={u,v,w};
}
buildKT();dfs(num,1);init();
while(q--)
{
read(k);
for(int i=1;i<=k;i++)
{
read(a[i]);a[i]^=ans;
}
sort(a+1,a+1+k,[](int x,int y)->bool{return id[x]<id[y];});
ans=0;
for(int i=1;i<k;i++)ans=max(ans,val[lca(a[i],a[i+1])]);
printf("%d
",ans);
}
}
题意:
给一个 (n) 个节点 (m) 条带权边的无向连通图,有 (q) 次询问,对于询问的 (v,w),表示询问从图中点 (v) 开车出发,当遇到海拔 (l) 小于等于 (w) 时路径会积水,于是开始弃车步行,询问从点 (v) 出发到达 点 (1) 的最小步行距离。强制在线。
(1le nle2 imes10^5,;1le m,qle4 imes10^5)
题解:
以海拔为边的价值建 (kruscal) 重构树(小根堆),计算原图每个点到点 (1) 的最短距离,那么答案就是 点 (v) 的祖先中 满足海拔大于限制的 深度最小的祖先所在子树的所有叶子 到点 (1) 的最小距离。
求这个祖先用的是倍增。
为什么:因为那个祖先的子树的点都是符合要求的(可以开车到达的),那么就开车到达其中步行到点 (1) 距离最小的点,那样子结果就是最优的。
(code:)
//#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
#define reg register
#define ll long long
using namespace std;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int maxn=2e6+5;
const double ep=1e-12;
template<typename T>void read(T&x)
{
char c;
while(!isdigit(c=getchar()));x=c-'0';
while(isdigit(c=getchar()))x=(x<<1)+(x<<3)+c-'0';
}
int pre[maxn];
int find(int x)
{
int r=x,t;
while(r!=pre[r])r=pre[r];
while(r!=x)
t=pre[x],pre[x]=r,x=t;
return r;
}
struct E{
int u,v,w;
}e[maxn];
int n,m;
ll dis[maxn];
struct P{
int u;ll w;
bool operator<(const P&p)const{return w>p.w;}
};
vector<P>p[maxn];
priority_queue<P>que;
void Dij()
{
memset(dis,inf,sizeof dis);dis[1]=0;
que.push({1,0});
P poi;
while(!que.empty())
{
poi=que.top();que.pop();
for(P pv:p[poi.u])
if(dis[pv.u]>dis[poi.u]+pv.w)
{
dis[pv.u]=dis[poi.u]+pv.w;
que.push({pv.u,dis[pv.u]});
}
}
// for(int i=1;i<=n;i++)printf("%lld ",dis[i]);putchar(10);
}
int num,val[maxn],fa[maxn][25];
void buildKT()
{
sort(e+1,e+1+m,[](E p1,E p2)->bool{return p1.w>p2.w;});
for(int i=1,up=n+m;i<=up;i++)pre[i]=i;
num=n;
for(int i=1,fu,fv;i<=m;i++)
{
fu=find(e[i].u);fv=find(e[i].v);
if(fu!=fv)
{
val[++num]=e[i].w;
pre[fu]=pre[fv]=num;fa[fu][0]=fa[fv][0]=num;
dis[num]=min(dis[fu],dis[fv]);
// printf("[%d (%d) %d]
",fu,num,fv);
}
}
}
void init()
{
for(int j=1;(1<<j)<=num;j++)
for(int i=1;i<=num;i++)
if(fa[i][j-1])fa[i][j]=fa[fa[i][j-1]][j-1];
}
int gettop(int u,int w)
{
for(int i=20;i>=0;i--)
if(fa[u][i]&&val[fa[u][i]]>w)u=fa[u][i];
return u;
}
int main()
{
int T,u,v,w,l,q,k,s;ll ans;
read(T);
while(T--)
{
num=0;ans=0;
memset(fa,0,sizeof(fa));
read(n);read(m);
for(int i=1;i<=n;i++)p[i].clear();
for(int i=1;i<=m;i++)
{
read(u);read(v);read(l),read(w);
e[i]={u,v,w};
p[u].push_back({v,l});p[v].push_back({u,l});
}
Dij();buildKT();init();
read(q);read(k);read(s);
while(q--)
{
read(v);read(w);
v=(ans*k+v-1)%n+1;
w=(ans*k+w)%(s+1);
ans=dis[gettop(v,w)];
printf("%lld
",ans);
}
}
}
新技能get!