zoukankan      html  css  js  c++  java
  • Kruskal重构树

    先推荐一篇对我帮助很大的博客:Kruskal重构树入门

    前置知识

    1、(Kruskal)算法

    2、基础数据结构,如主席树

    简述

    (Kruskal)重构树与(Kruskal)算法密切相关。

    我们知道,(Kruskal)算法是按照边权排序,依次合并节点,并用并查集维护联通。

    (Kruskal)重构树则是合并节点(x,y)时,断开(x,y)的连边,新建一个节点(z),将(z)作为(x,y)的父亲,同时把((x,y))的边权作为(z)的点权,然后用并查集维护联通。


    那么它有一些很有用的性质。

    1、它是一个二叉堆。

    2、若边权升序,则它是一个大根堆

    3、任意两点路径边权最大值为(Kruskal)重构树上(LCA)的点权。

    实现大致如下:

    int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    void Kruskal()
    {
    	sort(E+1, E+m+1, cmp);
    	for (int i=1; i<=n; i++) fa[i]=i; int now=n;
    	for (int i=1; i<=m; i++)
    	{
        	int x=E[i].x, y=E[i].y, w=E[i].w;
        	int fx=find(x), fy=find(y);
        	if (fx^fy)
        	{
            	val[++now]=w; fa[fx]=fa[fy]=fa[now]=now; 
            	add(now, fx, 1); add(now, fy, 1);
        	}
    	}
    }
    

    例题

    我们来看一道例题:

    bzoj3551 Peaks

    题目传送门

    你没发现这是两个链接吗

    Description

    (N​)座山峰,有高度(h_i​)。山峰间有(M​)条双向边,有边权。(Q​)组询问,询问从(v​)开始经过边权不超过(x​)能到达的山峰中第(k​)高。强制在线。

    Solution

    显然,一个点能到达,在最小生成树上必定能到达。但建出最小生成树并没有什么用,于是我们建出(Kruskal)重构树,然后发现(dfs)+主席树静态区间(K)大就可以了。

    代码如下(本代码为洛谷4197AC代码):

    #include<bits/stdc++.h>
    using namespace std;
    const int N=200005, M=500005;
    struct node{int x, y, w;}E[M];
    struct NODE{int to, nxt;}edge[N];
    int size[N], deg[N], fa[N], dfn[N];
    int in[N], out[N], h[N>>1], val[N];
    int f[N][30], head[N], cnt, Cnt, tot, n, m, q;
    int rt[N], ls[N<<5], rs[N<<5], sum[N<<5];
    
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
        return x*f;
    }
    
    void add(int u, int v)
    {
        edge[++Cnt]=(NODE){v, head[u]};
        head[u]=Cnt; deg[v]++;
    }
    
    bool cmp(node a, node b){return a.w<b.w;}
    int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    
    void dfs(int u)
    {
        dfn[++tot]=u; in[u]=tot; 
        for (int i=1; (1<<i)<=(n<<1); i++) f[u][i]=f[f[u][i-1]][i-1];
        for (int i=head[u]; i; i=edge[i].nxt)
        {
            int v=edge[i].to; if (v==f[u][0]) continue;
            f[v][0]=u; dfs(v); size[u]+=size[v];
        }
        if (!size[u]) size[u]=1; out[u]=tot;
    }
    
    void insert(int &o, int l, int r, int x, int v)
    {
        ls[++cnt]=ls[o]; rs[cnt]=rs[o]; 
        sum[cnt]=sum[o]+v; o=cnt;
        if (l==r) return; int mid=(l+r)>>1;
        if (x<=mid) insert(ls[o], l, mid, x, v);
            else insert(rs[o], mid+1, r, x, v);
    }
    
    int kth(int u, int v, int l, int r, int k)
    {
        if (l==r) return l; 
        int mid=(l+r)>>1, s=sum[rs[v]]-sum[rs[u]];
        if (k<=s) return kth(rs[u], rs[v], mid+1, r, k);
            else return kth(ls[u], ls[v], l, mid, k-s);
    }
    
    int query(int v, int x, int k)
    {
        for (int i=25; ~i; i--) 
            if (f[v][i] && val[f[v][i]]<=x) v=f[v][i];
        if (size[v]<k) return -1;
        return kth(rt[in[v]-1], rt[out[v]], 0, 1e9, k);
    }
    
    int main()
    {
        n=read(); m=read(); q=read(); int nn=n;
        for (int i=1; i<=n; i++) h[i]=read();
        for (int i=1; i<=m; i++) E[i]=(node){read(), read(), read()};
        sort(E+1, E+m+1, cmp);
        for (int i=1; i<=(n<<1); i++) fa[i]=i;
        for (int i=1; i<=m; i++)
        {
            int fx=find(E[i].x), fy=find(E[i].y);
            if (fx^fy)
            {
                val[++nn]=E[i].w; fa[fx]=fa[fy]=nn;
                add(nn, fx); add(nn, fy); 
            }
        }
        for (int i=1; i<=nn; i++) if (!deg[i]) dfs(i);
        for (int i=1; i<=nn; i++)
        {
            rt[i]=rt[i-1]; 
            if (dfn[i]<=n) insert(rt[i], 0, 1e9, h[dfn[i]], 1);
        }
        for (int i=1; i<=q; i++)
        {
            int v=read(), x=read(), k=read();
            printf("%d
    ", query(v, x, k));
        }
        return 0;
    }
    
    

    再看一题

    NOI2018 归程

    题目传送门

    机房神犇(zzr)用200+行可持久化并查集大力秒题,可是我不会。

    Description

    (N)个点(M)条边的连通图,用(l, a)表示长度,海拔。

    (Q​)次询问,每次询问给定起点(v ​),水位线(p​),求经过海拔不低于(p​)能到达的点中距离1号节点最近的距离。

    Solution

    建立以海拔为关键字的(Kruskal)重构树(海拔为降序),那么能到达的点在(Kruskal)重构树的一个子树中。

    那么就很清晰了,先用(Dijkstra)预处理单源最短路,再构建(Kruskal)重构树,然后(dfs)维护每个店为根的子树中距1号点的最小距离。处理询问时树上倍增即可。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=800005;
    struct node{int x, y, l;}E[N];
    struct Edge{int to, nxt, w;}edge[N];
    struct heap{int u, w;};
    int f[N][21], fa[N], vis[N], dis[N], mn[N], val[N];
    int head[N], cnt, n, m;
    bool operator < (heap a, heap b){return a.w>b.w;}
    bool cmp(node a, node b){return a.l>b.l;}
    int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
        return x*f;
    }
    
    void add(int u, int v, int w)
    {
        edge[++cnt]=(Edge){v, head[u], w};
        head[u]=cnt;
    }
    
    void Dijkstra()
    {
        memset(vis, 0, sizeof(vis)); vis[1]=1;
        memset(dis, 0x3f, sizeof(dis)); dis[1]=0;
        priority_queue<heap> q; q.push((heap){1, 0});
        while (!q.empty())
        {
            int u=q.top().u; q.pop();
            for (int i=head[u]; i; i=edge[i].nxt)
            {
                int v=edge[i].to, w=edge[i].w;
                if (dis[v]>dis[u]+w)
                    dis[v]=dis[u]+w, q.push((heap){v, dis[v]});
            }
        }
    }
    
    void dfs(int u, int fa)
    {
        f[u][0]=fa; mn[u]=dis[u];
        for (int i=1; i<=20; i++) f[u][i]=f[f[u][i-1]][i-1];
        for (int i=head[u]; i; i=edge[i].nxt)
        {
            int v=edge[i].to; 
            dfs(v, u); mn[u]=min(mn[u], mn[v]);
        }
    }
    
    void Kruskal()
    {
        sort(E+1, E+m+1, cmp);
        for (int i=1; i<=n; i++) fa[i]=i; int now=n;
        memset(head, 0, sizeof(head)); cnt=0;
        for (int i=1; i<=m; i++)
        {
            int x=E[i].x, y=E[i].y, w=E[i].l;
            int fx=find(x), fy=find(y);
            if (fx^fy)
            {
                val[++now]=w; fa[fx]=fa[fy]=fa[now]=now; 
                add(now, fx, 1); add(now, fy, 1);
            }
        }
        dfs(now, 0);
    }
    
    int main()
    {
        int T=read();
        while (T--)
        {
            memset(head, 0, sizeof(head)); cnt=0;
            memset(mn, 0, sizeof(mn));
            memset(f, 0, sizeof(f));
            n=read(); m=read();
            for (int i=1; i<=m; i++)
            {
                int u=read(), v=read(), l=read(), a=read();
                E[i]=(node){u, v, a}; add(u, v, l); add(v, u, l);
            }
            Dijkstra(); Kruskal();
            int q=read(), k=read(), s=read();
            for (int i=1, last=0; i<=q; i++)
            {
                int v=(read()+k*last-1)%n+1, p=(read()+k*last)%(s+1);
                for (int j=20; ~j; j--)
                    if (f[v][j] && val[f[v][j]]>p) v=f[v][j];
                printf("%d
    ", last=mn[v]);
            }	
        }
        return 0;
    }
    
    

    最后一题

    此题借鉴了Dance_Of_Faith大佬的博客。

    IOI2018 Werewolf狼人

    Description

    (N)个点(M)条边的无向连通图。对于每次询问,给定(S,E,L,R),求出能否从(S)出发,前一段只经过(L)(N)的点,后一段只经过(1)(R)的点,到达(E)点。

    Solution

    我们构建两棵Kruskal重构树。

    一棵从大到小枚举点(u),枚举连边且编号比它大的(v),让(v)所在集合为(u)的儿子节点,那么从(S)出发前一段能经过的点在一个子树中,用树上倍增即可找到这个子树。

    (E​)能到达的后一段也可同理求得。

    接下来要找到一个前后两段的分界线,我们只需考虑每一个点在两棵树的(dfs)序是否都在每一次询问的(S)(E)的出入的(dfs)序的区间中。这是经典的二维数点问题,离线用树状数组解决,在线用主席树解决。

    因为本人比较懒,只给出了离线树状数组实现的代码。

    #include<cstdio>
    #include<vector>
    #include<algorithm>
    using namespace std;
    const int N=200005;
    struct node{int x, y, id, typ, sig;}q[N<<3];
    bool operator < (node a, node b){return a.x<b.x || (a.x==b.x && a.typ<b.typ);}
    vector<int> G[N];
    int ans[N], d[N], n, m, Q;
    
    struct dsu
    {
        int fa[N];
        void init(){for (int i=1; i<=n; i++) fa[i]=i;}
        int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
    }u, v;
    
    struct Kruskal
    {
        vector<int> G[N];
        int in[N], out[N], deg[N], dep[N], rnk[N], f[N][22], tot;
        void add(int u, int v){G[u].push_back(v); deg[v]++;}
        void dfs(int u, int fa)
        {
            in[u]=++tot; rnk[tot]=u;
            dep[u]=dep[fa]+1; f[u][0]=fa;
            for (int i=1; i<=20; i++) f[u][i]=f[f[u][i-1]][i-1];
            for (int v: G[u]) dfs(v, u);
            out[u]=tot;
        }
        void build()
        {
            int rt; for (int i=1; i<=n; i++) if (!deg[i]) rt=i;
            dfs(rt, 0);
        }
    }U, V;
    
    int getfa_U(int u, int anc)
    {
        for (int i=20; ~i; i--) 
            if (U.f[u][i] && U.f[u][i]>=anc) u=U.f[u][i];
        return u;
    }
    int getfa_V(int v, int anc)
    {
        for (int i=20; ~i; i--)
            if (V.f[v][i] && V.f[v][i]<=anc) v=V.f[v][i];
        return v;
    }
        
    
    void add(int x){for (; x<=n; x+=x&-x) d[x]++;}
    int query(int x){int res=0; for (; x; x-=x&-x) res+=d[x]; return res;}
    
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
        for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
        return x*f;
    }
    
    int main()
    {
        n=read(); m=read(); Q=read();
        for (int i=1; i<=m; i++)
        {
            int u=read()+1, v=read()+1;
            G[u].push_back(v); 
            G[v].push_back(u);
        }
        u.init(); v.init();
        
        for (int i=n; i; i--)
            for (int j: G[i]) if (j>i) 
            {
                int k=u.find(j);
                if (i^k) U.add(i, k), u.fa[k]=u.fa[i];
            }
        for (int i=1; i<=n; i++)
            for (int j: G[i]) if (j<i)
            {
                int k=v.find(j); 
                if (i^k) V.add(i, k), v.fa[k]=v.fa[i];
            }
        U.build(); V.build();
        
        for (int i=1; i<=n; i++)
            q[i].x=U.in[i], q[i].y=V.in[i], q[i].typ=0;
        int cnt=n;
        for (int i=1; i<=Q; i++) 
        {
            int x=read()+1, y=read()+1, l=read()+1, r=read()+1;
            int u=getfa_U(x, l), v=getfa_V(y, r);
            q[++cnt]=(node){U.out[u], V.out[v], i, 1, 1};
            if (U.in[u]>1) 
                q[++cnt]=(node){U.in[u]-1, V.out[v], i, 1, -1};
            if (V.in[v]>1) 
                q[++cnt]=(node){U.out[u], V.in[v]-1, i, 1, -1};
            if (U.in[u]>1 && V.in[v]>1) 
                q[++cnt]=(node){U.in[u]-1, V.in[v]-1, i, 1, 1};
        }
        
        sort(q+1, q+cnt+1);
        for (int i=1; i<=cnt; i++)
            if (q[i].typ)
                ans[q[i].id]+=q[i].sig*query(q[i].y);
            else add(q[i].y);
        for (int i=1; i<=Q; i++) printf("%d
    ", (bool)ans[i]);
        return 0;
    }
    
    

    写在最后

    其实有很多题目能够用(Kruskal)重构树暴力优雅地写过,例如(NOIp2013)货车运输。所以看到求经过权值不超过(k)的点或边能到达的点的相关的信息时,就可以艰难简单地考虑用(Kruskal ​)重构树来做啦。

  • 相关阅读:
    什么企业邮箱安全性高?国内哪家企业邮箱好用?
    163VIP邮箱外贸群发技巧有哪些?
    163邮箱如何群发邮件?外贸邮箱群发哪个效果好?
    登录163邮箱续费情况怎么查询?163vip邮箱怎么收费?
    Java面试题总结之Java基础(二)
    Java面试题总结之Java基础(一)
    Exception in thread "baseScheduler_QuartzSchedulerThread" java.lang.OutOfMemoryError: GC
    Maven项目报错:java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
    如何下载Github上项目源码打包到本地Maven仓库
    HTML日期时间插件
  • 原文地址:https://www.cnblogs.com/ACMSN/p/10646306.html
Copyright © 2011-2022 走看看