【树】kruskal重构树

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);
    }
}

2,luoguP4768 [NOI2018]归程

题意:

给一个 (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!

原文地址:https://www.cnblogs.com/kkkek/p/12884368.html