码迷,mamicode.com
首页 > 其他好文 > 详细

【树】kruskal重构树

时间:2020-05-13 20:15:19      阅读:46      评论:0      收藏:0      [点我收藏+]

标签:二叉堆   clear   build   acm   time   const   struct   就是   type   

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\) 的最小值。强制在线。

\(1\le n,m\le5\times10^5,\;1\le\sum_{i=1}^qk_i\le10^6,\;1\le k_i\le n,\;1\le u,v\le n,\;1\le w_i\le 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]\n",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\n",ans);
    }
}

2,luoguP4768 [NOI2018]归程

题意:

给一个 \(n\) 个节点 \(m\) 条带权边的无向连通图,有 \(q\) 次询问,对于询问的 \(v,w\),表示询问从图中点 \(v\) 开车出发,当遇到海拔 \(l\) 小于等于 \(w\) 时路径会积水,于是开始弃车步行,询问从点 \(v\) 出发到达 点 \(1\) 的最小步行距离。强制在线。

\(1\le n\le2\times10^5,\;1\le m,q\le4\times10^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]\n",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\n",ans);
        }
    }
}

新技能get!

【树】kruskal重构树

标签:二叉堆   clear   build   acm   time   const   struct   就是   type   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!