1. 程式人生 > 其它 >Kruskal重構樹

Kruskal重構樹

Kruskal 重構樹

簡介

Kruskal重構樹其實就是在Kruscal演算法進行的過程中,每一次加邊都會合並兩個集合,我們可以新建一個點,將這個點的點權設為新加的邊的權值,同時將兩個集合的根節點分別設為新建的點的左兒子和右兒子。然後將兩個集合和新建點合併成一個集合。將新建點設為根。

這樣建成的一棵二叉樹就是Kruskal重構樹。

如:

此圖的Kruskal重構樹如下:

性質

  • 該樹滿足二叉堆的性質;
  • 這顆二叉樹的節點個數為\(2n-1\) ,深度最大為\(n\);
  • 重構樹中代表原樹中的點的節點全是葉子節點,其餘節點都代表了一條邊的邊權;
  • 原圖中兩個點之間的所有簡單路徑上最大邊權的最小值 = Kruskal 重構樹上兩點之間的 LCA 的權值

因為在加邊的時候所有的邊都是已經排序過的所以符合二叉堆的性質。

又因為二叉堆的性質所以 **Kruskal 重構樹上兩點之間的 LCA 的權值即為原圖中兩個點之間的所有簡單路徑上最大邊權的最小值 **。

若求兩個點之間的所有簡單路徑上最小邊權的最大值,要建最大生成樹

構造

首先對邊排序

然後使用並查集輔助加邊:

每新建一條邊時,新建一個點,設新建點的權值為新加邊的邊權,合併集合。

程式碼:

void kruskal()
{
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n*2;i++)f[i]=i;// 2n-1個節點
    for(int i=1;i<=m;i++)
    {
        if(cnt==2*n-1)break;
        int x=Find(e[i].u),y=Find(e[i].v);
        if(x==y)continue;
        cnt++;
        f[x]=f[y]=cnt;
        tree[cnt].push_back(y);
        tree[cnt].push_back(x);
        w[cnt]=e[i].c;
    }
    return;
}

時間複雜度\(O(nlog_2n)\)

應用

**Kruskal重構樹能夠更快有效解決一些靜態的樹剖問題,而且複雜度還很優秀。 **

luogu U92652 【模板】Kruskal重構樹

一道板子題(注意建成的應是一個森林)。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int Max=300010;
int n,m,q,cnt;
struct node{
    int u,v,c;
}e[Max];
int f[Max*2],w[Max*2],lg[Max*2];
int depth[Max*2],fa[Max*2][30];
vector<int> mp[Max*2];
int cmp(node x,node y)
{
    return x.c<y.c;
}
int Find(int x)
{
    if(x==f[x])return x;
    return f[x]=Find(f[x]);
}
void kruskal()
{
    for(int i=1;i<=n*2;i++)f[i]=i;
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int x=Find(e[i].u),y=Find(e[i].v);
        if(x==y)continue;
        cnt++;
        f[x]=cnt;f[y]=cnt;
        w[cnt]=e[i].c;
        mp[cnt].push_back(x);
        mp[cnt].push_back(y);
    }
    return;
}
void dfs(int now,int father)
{
    depth[now]=depth[father]+1;
    fa[now][0]=father;
    for(int i=1;i<=lg[depth[now]];i++)
        fa[now][i]=fa[fa[now][i-1]][i-1];
    for(int i=0;i<mp[now].size();i++)
    {
        int x=mp[now][i];
        if(depth[x])continue;
        dfs(x,now);
    }
    return;
}
int LCA(int x,int y)
{
    if(Find(x)!=Find(y))return -1;
    if(depth[x]<depth[y])swap(x,y);
    while(depth[x]>depth[y])
        x=fa[x][lg[depth[x]-depth[y]]];
    if(x==y)return w[x];
    for(int i=lg[depth[x]];i>=0;i--)
    {
        if(fa[x][i]==fa[y][i])continue;
        x=fa[x][i];
        y=fa[y][i];
    }
    return w[fa[x][0]];
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    cnt=n;
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
    kruskal();
    for(int i=1;i<=n;i++)
        lg[i]=lg[i-1]+(i==1<<(lg[i-1]+1));
    for(int i=1;i<=cnt;i++)
    {
        if(f[i]!=i)continue;
        if(depth[f[i]])continue;
        dfs(f[i],0);
    }
    for(int i=1;i<=q;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

luogu P1967 貨車運輸

其實該問題可以轉換為\(x\)號城市到\(y\)號城市所有簡單路徑上小邊權的最大值(注意該圖建成的為一個森林)。

程式碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
const int maxm=5e4+10;
int n,m,q,cnt;
struct node{
    int u,v,c;
}e[maxm];
int f[maxn*2],depth[maxn*2],father[maxn*2][30];
int lg[maxn*2],w[maxn*2];
vector<int> mp[maxn*2];
int cmp(node x,node y)
{
    return x.c>y.c;
}
int Find(int x)
{
    if(x==f[x])return x;
    return f[x]=Find(f[x]);
}
void kruscal()
{
    for(int i=1;i<=n*2;i++)f[i]=i;
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        int x=Find(e[i].u),y=Find(e[i].v);
        if(x==y)continue;
        cnt++;
        f[x]=f[y]=cnt;w[cnt]=e[i].c;
        mp[cnt].push_back(x);
        mp[cnt].push_back(y);
    }
    return;
}
void dfs(int now,int fa)
{
    depth[now]=depth[fa]+1;
    father[now][0]=fa;
    for(int i=1;i<=lg[depth[now]];i++)
        father[now][i]=father[father[now][i-1]][i-1];
    for(int i=0;i<mp[now].size();i++)
    {
        int x=mp[now][i];
        if(depth[x])continue;
        dfs(x,now);
    }
    return;
}
int LCA(int x,int y)
{
    if(Find(x)!=Find(y))return -1;
    if(depth[x]<depth[y])swap(x,y);
    while(depth[x]>depth[y])
        x=father[x][lg[depth[x]-depth[y]]];
    if(x==y)return w[x];
    for(int i=lg[depth[x]];i>=0;i--)
    {
        if(father[x][i]==father[y][i])continue;
        x=father[x][i];y=father[y][i];
    }
    return w[father[x][0]];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
    cnt=n;
    kruscal();
    for(int i=1;i<=n;i++)
        lg[i]=lg[i-1]+(i==1<<(lg[i-1]+1));
    for(int i=1;i<=cnt;i++)
    {
        if(f[i]!=i)continue;
        if(depth[f[i]])continue;
        dfs(f[i],0);
    }
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

luogu P4768 [NOI2018] 歸程

這道題主要在於求最小的步行路程,不用考慮開車所經過的路程

所以我們只需要兩點之間路徑上的邊權最小值最大即可,設這個值為\(h\),若\(h>S\),則步行路程為零,若\(h\le S\),我們便需要去判斷在哪裡下車。

在Kruskal重構樹中,任意節點的子樹節點都是可以相互抵達的,所以我們可以用樹上倍增去求出,這棵樹上大於水位線的最小值,然後輸出這個節點子樹中,到達終點的最短路的最小值。

最短路+樹上倍增+Kruskal重構樹。

程式碼:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+10;
const int maxm=4e5+10;
const ll INF=5147483647;
int T,n,m,q,k,s,cnt;ll lastans;
int f[maxn*2],lg[maxn];
int depth[maxn*2],fa[maxn*2][31];
struct node{
    int u,v,l,a;
}e[maxm];
struct road{
    int to,cost;
};
struct power{
    ll w,l;
}p[maxn*2];
vector<road> mp[maxn];
vector<int> tree[maxn*2];
typedef pair<ll,int> Pair;
void _clean()
{
    memset(f,0,sizeof(f));
    memset(depth,0,sizeof(depth));
    for(int i=0;i<=cnt;i++)
        for(int j=0;j<=30;j++)
            fa[i][j]=0;
    for(int i=1;i<=n;i++)mp[i].clear();
    for(int i=1;i<=cnt;i++)tree[i].clear();
    cnt=0;lastans=0;
}
void makelog()
{
    for(int i=1;i<=maxn;i++)
        lg[i]=lg[i-1]+(i==1<<(lg[i-1]+1));
    return;
}
bool cmp(node x,node y)
{
    return x.a>y.a;
}
int Find(int x)
{
    if(x==f[x])return x;
    return f[x]=Find(f[x]);
}
void kruscal()
{
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n*2;i++)f[i]=i;
    for(int i=1;i<=m;i++)
    {
        if(cnt==2*n-1)break;
        int x=Find(e[i].u),y=Find(e[i].v);
        if(x==y)continue;
        cnt++;
        f[x]=f[y]=cnt;
        tree[cnt].push_back(y);
        tree[cnt].push_back(x);
        p[cnt].w=e[i].a;
    }
    for(int i=1;i<=cnt;i++)p[i].l=INF;
    return;
}
void dij()
{
    priority_queue<Pair,vector<Pair>,greater<Pair> > q;
    bool vis[maxn];ll dis[maxn];
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++)dis[i]=INF;
    dis[1]=0;
    q.push(make_pair(0,1));
    while(!q.empty())
    {
        int x=q.top().second;
        int c=q.top().first;
        q.pop();
        if(vis[x])continue;
        vis[x]=true;
        for(int i=0;i<mp[x].size();i++)
        {
            if(dis[mp[x][i].to]>dis[x]+mp[x][i].cost)
            {
                dis[mp[x][i].to]=dis[x]+mp[x][i].cost;
                q.push(make_pair(dis[mp[x][i].to],mp[x][i].to));
            }
        }
    }
    for(int i=1;i<=n;i++)p[i].l=dis[i];
    return;
}
void dfs(int now,int father)
{
    depth[now]=depth[father]+1;
    fa[now][0]=father;
    for(int i=1;i<=lg[depth[now]];i++)
        fa[now][i]=fa[fa[now][i-1]][i-1];
    for(int i=0;i<tree[now].size();i++)
    {
        dfs(tree[now][i],now);
        p[now].l=min(p[now].l,p[tree[now][i]].l);// 更新最短路最小值
    }
    return;
}
ll ask(int x,int y)
{
    for(int i=lg[depth[x]];i>=0;i--)
        if(p[fa[x][i]].w>y)x=fa[x][i];
    return p[x].l;
}
int main()
{
    makelog();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        cnt=n;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].l,&e[i].a);
            mp[e[i].u].push_back((road){e[i].v,e[i].l});
            mp[e[i].v].push_back((road){e[i].u,e[i].l});
        }
        kruscal();
        dij();
        dfs(cnt,0);
        scanf("%d%d%d",&q,&k,&s);
        for(int i=1;i<=q;i++)
        {
            int x,y;scanf("%d%d",&x,&y);
            int vx=(x+k*lastans-1)%n+1;
            int py=(y+k*lastans)%(s+1);
            lastans=ask(vx,py);
            printf("%lld\n",lastans);
        }
        _clean();
    }
    return 0;
}

完結撒花~~