Kruskal重构树—简介与基本应用

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/83474993

Kruskal重构树就是在做kruskal是把边权改成点权,一个n个点的图可以生成2*n-1个点的kruskal重构树。
一篇很棒的博客https://blog.csdn.net/hwzzyr/article/details/81190442

构造

维护一个并查集。把图上的边按边权值递增排序,每次取出最小的边,用并查集判断其两端的点是否已相连。如果是,那么直接略过这条边;否则,建一个新点r,点权为该边边权。记两端点的祖先节点为fx,fy,r分别fx和fy连一条边,并且把r记为父亲,同时直线两端的点的祖先也被更新为r。
最终会新建n-1个点来连接这些原有的点。

性质

1、Kruskal重构树是一个二叉堆,即每一个儿子节点的权值都小于等于自己的权值;
2、连接两点路径上的最大边权的最小值是这两个点在kruskal重构树上的lca的点权;(一般用的是这条性质)
3、原图中的点处在叶子节点的位置。

代码

int fa[MAXN*2];
int id,w[MAXN*2];
int findfa(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=findfa(fa[x]);
}
bool cmp(E e1,E e2){return e1.c<e2.c;}
void ex_kruskal()//最终会生成2*n-1个点 
{
    id=n;//id从n+1开始
    for(int i=1;i<2*n;i++) fa[i]=i;
    sort(a+1,a+m+1,cmp);//按边权排序
    for(int i=1;i<=m;i++)
    {
        int fx=findfa(a[i].x),fy=findfa(a[i].y);
        if(fx!=fy)//不相连
        {
            w[++id]=a[i].c;//获得点权
            fa[fx]=id;fa[fy]=id;
            ins(id,fx);ins(id,fy);//建边
            if(id==2*n-1) return ;
        }
    }
}

例题1

(bzoj3732 Network)给你N个点的无向图 (1 <= N <= 15,000),记为:1…N。 
图中有M条边 (1 <= M <= 30,000) ,第j条边的长度为: d_j ( 1 < = d_j < = 1,000,000,000).
现在有 K个询问 (1 < = K < = 20,000)。
每个询问的格式是:A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

题解

这题用来练模版...

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=15010,MAXM=30010;

int n,m,Q;

struct E{int x,y,c,next;}a[MAXM],e[MAXN*2];int len=1,last[MAXN*2];//debug 分清a,e 
bool cmp(E e1,E e2){return e1.c<e2.c;}
void ins(int x,int y)
{
    e[++len]=(E){x,y,0,last[x]};last[x]=len;
}

int fa[MAXN*2];
int id,w[MAXN*2];
int findfa(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=findfa(fa[x]);
}
void ex_kruskal()//最终会生成2*n-1个点 
{
    id=n;
    for(int i=1;i<2*n;i++) fa[i]=i;//debug 把所有会用到的点(1~2*n-1)全部初始化 
    sort(a+1,a+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int fx=findfa(a[i].x),fy=findfa(a[i].y);
        if(fx!=fy)
        {
            w[++id]=a[i].c;
            fa[fx]=id;fa[fy]=id;//debug 错误更新fa[fx]=fy;//debug 千万不要忘记辅助数组的更新 
            ins(id,fx);ins(id,fy);
            if(id==2*n-1) return ;
        }
    }
}

int head,tail,q[MAXN*2];
int dep[MAXN*2],f[MAXN*2][20];
void bfs()
{
    dep[id]=1;
    head=0,tail=1;q[0]=id;//debug 从last_id开始 
    while(head<tail)
    {
        int x=q[head++];
        for(int k=last[x];k;k=e[k].next)
        {
            int y=e[k].y;
            dep[y]=dep[x]+1;
            f[y][0]=x;for(int i=1;i<=18;i++) f[y][i]=f[f[y][i-1]][i-1];
            q[tail++]=y;
        }
    }
}
int lca(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=18;i>=0;i--)
        if(dep[f[x][i]]>=dep[y]) x=f[x][i];
    if(x==y) return x;
    for(int i=18;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

int main()
{
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].c);
    ex_kruskal();
    bfs();
    while(Q--)
    {
        int x,y,p;
        scanf("%d%d",&x,&y);p=lca(x,y);
        printf("%d\n",w[p]);
    }
    return 0;
}

例题2

NOIP2013 货车运输

题解

此题很思维,但是如果知道kruskal之后就很容易了,题解看这

例题3

(bzoj3551 Peaks)(洛谷4197 Peaks)在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。

题解

kruskal重构树+主席树
先建出kruskal重构树,给定起点v和最大的困难度x之后,相当于就限制在了一棵子树里。这棵子树的根节点可以用倍增来求。
接下来的问题相当于是在一棵字数中求第k大的点。那么用主席树可以解决。
为了转化成主席树可以接受的线性结构,我们需要对树跑一遍dfs,找出每个节点的dfs序。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=1e5+10,MAXM=5e5+10;

int n,m,Q;
int w[MAXN],t[MAXN];//w记录实际高度,t用于离散化 

struct E{int x,y,c,next;}b[MAXM],e[MAXN*2];int len=0,last[MAXN*2];
bool cmp(E e1,E e2){return e1.c<e2.c;}
void ins(int x,int y){e[++len]=(E){x,y,0,last[x]};last[x]=len;}

int fa[MAXN*2];
int id,val[MAXN*2];
int findfa(int x){return fa[x]==x?x:fa[x]=findfa(fa[x]);}
void kruskal()
{
    id=n;
    sort(b+1,b+m+1,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1,tt=0;i<=m;i++)
    {
        int fx=findfa(b[i].x),fy=findfa(b[i].y);
        if(fx==fy) continue;
        val[++id]=b[i].c;
        ins(id,fx);ins(id,fy);
        fa[fx]=fa[fy]=fa[id]=id;
        if(++tt==n-1) break;
    }
}

int T=0,a[MAXN*2],L[MAXN*2],R[MAXN*2];//a[dfs序]=原节点编号 
int f[MAXN*2][20];
void dfs(int x)
{
    L[x]=++T;a[T]=x;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        f[y][0]=x;for(int i=1;i<=19;i++) f[y][i]=f[f[y][i-1]][i-1];
        dfs(y);//debug
    }
    R[x]=T;
}

int tot=0,rt[MAXN*30];
int sum[MAXN*30],ls[MAXN*30],rs[MAXN*30];//sum子树大小,ls,rs左右孩子 
void insert(int &now,int pre,int l,int r,int x)
{
    now=++tot;sum[now]=sum[pre]+1;
    ls[now]=ls[pre];rs[now]=rs[pre];
    if(l>=r) return ;
    int mid=l+r>>1;
    if(x<=mid) insert(ls[now],ls[pre],l,mid,x);
    else insert(rs[now],rs[pre],mid+1,r,x);
}
int query(int now,int pre,int l,int r,int x)
{
    if(l>=r) return l;
    int mid=l+r>>1,tmp=sum[ls[now]]-sum[ls[pre]];
    if(x<=tmp) return query(ls[now],ls[pre],l,mid,x);
    else return query(rs[now],rs[pre],mid+1,r,x-tmp);
}

int main()
{
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]),t[i]=w[i];
    for(int i=1;i<=m;i++) scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].c);
    
    sort(t+1,t+n+1);
    int ct=unique(t+1,t+n+1)-(t+1);
    for(int i=1;i<=n;i++) w[i]=lower_bound(t+1,t+ct+1,w[i])-t;
    
    kruskal();val[0]=INF;//debug 防止后面跑出树外 
    dfs(id);
    
    for(int i=1;i<=id;i++)//按dfs序插入 
        if(a[i]<=n) insert(rt[i],rt[i-1],1,ct,w[a[i]]);
        else rt[i]=rt[i-1];
        
    while(Q--)
    {
        int x,y,k,ans;scanf("%d%d%d",&x,&y,&k);
        for(int i=19;i>=0;i--) if(val[f[x][i]]<=y) x=f[x][i];
        int res=sum[rt[R[x]]]-sum[rt[L[x]-1]];
        if(res<k) ans=-1;
        else ans=t[query(rt[R[x]],rt[L[x]-1],1,ct,res-k+1)];//第k大相当于第res-k+1小
        printf("%d\n",ans);
    }
    return 0;
}

例题4

洛谷4768 [NOI2018]归程

题解

dijkstra最短路+kruskal重构树
跑一遍dijkstra可以找到所有点到1的最短距离d[i]。因为车是随便开的,所以在可以随意移动的范围中找到一个离x最近的点再走过去就OK了。
kruskal重构树可以找到可以随意移动的子树,同时对于那些新建的节点的d定义为该子树中一节点到1的最小距离,所以d[i]=min(d[fx],d[fy])。
在这样预处理后,求答案就很简单了,就是两点lca的d值。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
const int MAXN=200010,MAXM=400010;

inline int read()
{
    int re=0;char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9') re=re*10+(ch^48),ch=getchar();
    return re;
}
inline void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10|48);
}

int n,m;

struct E{int x,y,c,next;}e[MAXM*2],bi[MAXM],Ne[MAXN*2];int len,last[MAXN];int Nlen,Nlast[MAXN*2];
bool cmp(E e1,E e2){return e1.c>e2.c;}
void ins(int x,int y,int c){e[++len]=(E){x,y,c,last[x]};last[x]=len;}
void Nins(int x,int y){Ne[++Nlen]=(E){x,y,0,Nlast[x]};Nlast[x]=Nlen;}

int d[MAXN*2];bool vis[MAXN];
priority_queue<pii,vector<pii>,greater<pii> > pq;
void dijkstra()
{
    memset(d,0x3f,n+1<<2);d[1]=0;
    memset(vis,false,n+1);
    pq.push(pii(0,1));
    while(!pq.empty())
    {
        int x=pq.top().second;pq.pop();
        if(vis[x]) continue;vis[x]=true;
        for(int k=last[x];k;k=e[k].next)
        {
            int y=e[k].y;
            if(d[y]>d[x]+e[k].c)
            {
                d[y]=d[x]+e[k].c;
                pq.push(pii(d[y],y));
            }
        }
    }
}

int fa[MAXN*2];
int id,val[MAXN*2];
int findfa(int x){return x==fa[x]?fa[x]:fa[x]=findfa(fa[x]);}
void kruskal()
{
    id=n;Nlen=0;memset(Nlast,0,2*n<<2);
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(bi+1,bi+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int fx=findfa(bi[i].x),fy=findfa(bi[i].y);
        if(fx==fy) continue;
        fa[fx]=fa[fy]=fa[++id]=id;
        Nins(id,fx);Nins(id,fy);
        val[id]=bi[i].c;d[id]=min(d[fx],d[fy]);
        if(id==2*n-1) return ;
    }
}

int f[MAXN*2][21];
int head,tail,q[MAXN*2];
void bfs()
{
    head=0,tail=1;q[0]=id;
    for(int i=0;i<=19;i++) f[id][i]=id;
    while(head<tail)
    {
        int x=q[head++];
        for(int k=Nlast[x];k;k=Ne[k].next)
        {
            int y=Ne[k].y;
            f[y][0]=x;for(int i=1;i<=19;i++) f[y][i]=f[f[y][i-1]][i-1];
            q[tail++]=y;
        }
    }
}

int query(int x,int p)
{
    for(int i=19;i>=0;i--) if(val[f[x][i]]>p) x=f[x][i];//倍增找到一个x满足val[x]>=p且val[f[x][0]]<p
    return d[x];
}

int main()
{
    int Case=read();
    while(Case--)
    {
        len=1;memset(last,0,n+1<<2);
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int u=read(),v=read(),l=read(),a=read();
            ins(u,v,l);ins(v,u,l);
            bi[i]=(E){u,v,a};
        }
        
        dijkstra();
        
        kruskal();
        bfs();
        
        int Q=read(),K=read(),S=read(),lastans=0;
        while(Q--)
        {
            int v=(read()+K*lastans-1)%n+1,p=(read()+K*lastans)%(S+1);
            write(lastans=query(v,p));puts("");
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/83474993
今日推荐