• 【树】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!

  • 相关阅读:
    oracle
    SQL Server- 行列转换 行转列,多行转多列
    oracle
    asp.net 去掉小数点后面多余的0,本身为0则不显示
    asp.net 去掉小数点后面多余的0,本身为0还是显示为0
    asp.net 经常用到需要判断文本框是否输入的数字是小数,有无正负,几位小数,可以封装一起判断
    ASP.NET gridview导出excel,防止繁体产生有乱码的方式
    SQL- @@ROWCOUNT -返回上一行执行影响的行行数
    mui-a标签跳转
    浏览器地址截取
  • 原文地址:https://www.cnblogs.com/kkkek/p/12884368.html
Copyright © 2020-2023  润新知