[学习笔记]kruskal重构树 && 并查集重构树

Kruskal 重构树

[您有新的未分配科技点][BZOJ3545&BZOJ3551]克鲁斯卡尔重构树

kruskal是一个性质优秀的算法

加入的边是越来越劣的

科学家们借这个特点尝试搞一点事情。

kruskal求最小生成树的过程,如果把加入的一个边新建一个节点的话,并且把k1,k2的father设为新点的话,会得到一个2*n大小的树

实际上已经非常明白地表示kruskal这个过程了。这个树叫kruskal重构树

每个点的权值定义为所代表的边的权值。叶子节点权值最优。

由于贪心,所以树上所有点,从儿子到祖先权值单调不优。(这是最关键的性质)

树的结构我们非常熟悉

所以各种算法纷至沓来。

具体怎么用?

BZOJ3545 Peaks:

在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走。

现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。N<=1e5,M,Q<=5*1e5

(不强制在线的话,直接离线排序+线段树合并或者启发式合并。(类似“永无乡”))

强制在线呢?

kruskal重构树登场!

对于只走小于x的边,会得到一个可以到达的联通块。

最小生成树的边上走,保证这个联通块是极大的。

联通块高度的第k大就是答案。

发现这个联通块对应重构树的一个子树!

重构树上,从v开始往上倍增找到最后一个权值小于等于x的点p

p对应的子树的叶子就是所有的联通块的点。(有点类似SAM的fail树?)

所以区间第k大,用dfs序处理一下,+主席树维护即可。

NOI2018归程

[NOI2018]归程 

如果想到kruskal重构树(感觉学了应该都能想到,毕竟适用面也不是很广),那么这题就水了。

所能开车到达的集合里选择距离家最近的点下车。

kruskal重构树建出来(最大生成树),叶子赋予一个dis到1的距离。

dis用dij跑(因为出题人卡spfa)

然后dfs重构树处理信息,然后倍增处理询问。大功告成!

代码:

(很丑陋,见缝插针,而且mi、dep数组其实根本不需要)

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=200000+5;
const int M=400000+5;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll dis[N];
ll ans[2*N];
int val[2*N];
int n,m;
struct node{
    int nxt,to;
    int val;
}e[4*N];
int hd[2*N],cnt;
void add(int x,int y,int z){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    e[cnt].val=z;
    hd[x]=cnt;
}
int tot;
namespace dij{
    
    bool vis[N];
    struct po{
        int x,d;
        po(int xx,int dd){
            x=xx;d=dd;
        }
        bool friend operator <(po a,po b){
            return a.d>b.d;
        }
    };
    priority_queue<po>q;
    void dij(){
        memset(vis,0,sizeof vis);
        memset(dis,0x3f,sizeof dis);
        while(!q.empty()) q.pop();
        dis[1]=0;
        q.push(po(1,0));
        while(!q.empty()){
            po now=q.top();q.pop();
            if(vis[now.x]) continue;
            vis[now.x]=1;
            dis[now.x]=now.d;
            for(reg i=hd[now.x];i;i=e[i].nxt){
                int y=e[i].to;
                if(vis[y]) continue;
                q.push(po(y,dis[now.x]+e[i].val));
            }
        }
        for(reg i=1;i<=n;++i){
            ans[i]=dis[i];
            //mi[i][0]=0x3f3f3f3f;
        }
    }
}

struct edge{
    int x,y;
    int l,a;
    bool friend operator <(edge a,edge b){
        return a.a>b.a;
    }
}b[M];
//represent the value of edge
namespace kruscal{
    int fa[2*N];
    int fin(int x){
        return fa[x]==x?x:fa[x]=fin(fa[x]);    
    }
    void main(){
        for(reg i=1;i<=n;++i) {
            fa[i]=i;val[i]=0x3f3f3f3f;
        }
        tot=n;
    //    cout<<" tot "<<tot<<endl;
        sort(b+1,b+m+1);
        for(reg i=1;i<=m;++i){
            int k1=fin(b[i].x),k2=fin(b[i].y);
        //    cout<<" bb "<<tot<<" "<<i<<" : "<<b[i].x<<" "<<b[i].y<<" rt "<<k1<<"  sand "<<k2<<endl;
            if(k1!=k2){
                
                ++tot;
                fa[tot]=tot;
                ans[tot]=inf;
                
                add(tot,k1,0);
                add(tot,k2,0);
                fa[k1]=fa[k2]=tot;
                val[tot]=b[i].a;
            }
        }
    }
    
}
int fa[2*N][20];
int mi[2*N][20];
int dep[2*N];

void dfs(int x,int d){
    dep[x]=d;
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        fa[y][0]=x;
        mi[y][0]=min(val[y],val[x]);
        dfs(y,d+1);
        ans[x]=min(ans[x],ans[y]);
    }
}
ll query(int x,int p){
    for(reg j=19;j>=0;--j){
        if(fa[x][j]){
            if(mi[x][j]>p) x=fa[x][j];
        }
    }
    return ans[x];
}
void clear(){
    memset(fa,0,sizeof fa);
    memset(mi,0x3f,sizeof mi);
    memset(dep,0,sizeof dep);
    memset(hd,0,sizeof hd);
    cnt=0;
}
int main(){
    int T;
    rd(T);
    while(T--){
        rd(n);rd(m);
        clear();
        for(reg i=1;i<=m;++i){
            rd(b[i].x);rd(b[i].y);rd(b[i].l);rd(b[i].a);
            add(b[i].x,b[i].y,b[i].l);
            add(b[i].y,b[i].x,b[i].l);
        }
        dij::dij();
    //    cout<<" after dij "<<endl;
        
        memset(hd,0,sizeof hd);
        cnt=0;
        kruscal::main();
    //    cout<<" after kruc "<<endl;
        
        dfs(tot,1);
//        cout<<" after dfs "<<endl;
        for(reg j=1;j<=19;++j){
            for(reg i=1;i<=tot;++i){
                fa[i][j]=fa[fa[i][j-1]][j-1];
                mi[i][j]=min(mi[i][j-1],mi[fa[i][j-1]][j-1]);
            }
        }
        
//        cout<<" tot "<<tot<<endl;
//        for(reg i=1;i<=tot;++i){
//            cout<<" ii "<<i<<" val "<<val[i]<<" ans "<<ans[i]<<" fafa "<<fa[i][0]<<endl;
//        }
        int q,k,s;
        rd(q);rd(k);rd(s);
        ll las=0;
        int v,p;
        while(q--){
            rd(v);rd(p);
            v=(v+k*las-1)%n+1;
            p=(p+k*las)%(s+1);
            printf("%lld
",las=query(v,p));
        }
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2019/1/8 18:49:37
*/

这个算法适用面:
在线处理询问在经过小于(大于)某个边权的边所能到的集合里的信息。

主要性质是祖先权值的单调不优。

这个算法是对kruskal求最小生成树的操作过程的树形结构化,从而精确剖析过程,使得维护方便。

有异曲同工之妙的是动态点分治的分治树,也是对操作过程的树形结构化。

还有一个例题:(NOI和IOI都考了诶热度真高)

[IOI2018] werewolf 狼人

[IOI2018] werewolf 狼人


upda:2019.2.13

并查集重构树

除了Kruskal重构树之外,还有一个东西叫做并查集重构树

引入例题

n个点,q次操作,每次加入一条边(不会重边自环),或者查询两个点最早什么时候连通

n,q<=1e5

(某次jzoj考试题)

还是考虑具体维护出操作的结构

用按秩合并并查集,边权就是这次加入的边的时间值

发现,两个点第一次连通,一定是两个点并查集上路径最大值!

由于按秩合并,树高logn,可以暴力查询

如果是最后询问,可以建倍增数组,loglogn复杂度

对于一般的图,边权sort,然后按照上述做即可

毒瘤一些:

1.sort用松式基排

2.并查集按秩合并+路径压缩,另外开一个邻接表记录树

接近O(n)

性质:
1.越靠上权值越劣

2.两点间最大权值就是连通时间

比较优劣

并查集重构树:

劣势:子树不是一个区间(因为没有附加点)

优势:空间小,好写好调,两点间信息可以O(loglogn)快速求出。

kruskal重构树:

反过来。

劣势:难写一些,容易写错。

优势:多了附加点,经过小于vi的边到达的区域是子树,加上dfn序列很好维护。

原文地址:https://www.cnblogs.com/Miracevin/p/10241179.html