洛谷P4197 Peaks(Kruskal重构树+倍增+dfs序+主席树)

题目

n(n<=1e5)座山峰,第i座高度hi(hi<=1e9),保证所有hi不同,

m(m<=5e5)条边的无向图,q(q<=5e5)组询问,

每次询问给出(v,x,k),

询问从点v出发,只能走不超过x(x<=1e9)的边时,

从高到低第k的山峰的点号,不存在输出-1

思路来源

https://www.cnblogs.com/zwfymqz/p/9683523.html

https://blog.csdn.net/qq_40400202/article/details/102461902?utm_source=app

前置知识(Kruskal重构树)

图片来自于自为风月马前卒博客,很通俗易懂了,

根x和根y合并的时候,新建一个点T,T同时是x和y的父亲,

此时合并并查集,并建树连边

性质

  1. 是一个二叉树(左右子节点x和y)
  2. 如果是按最小生成树建立的话,是一个大根堆(因为越往上合并权值越大)
  3. 任意两个点路径上边权的最大值,为它们的LCA的点权(令路径上的最大值最小,显然这条路径在最小生成树上)

题解

先建Kruskal重构树,将父亲tot的权值设为根x和根y的边权阈值,

倍增预处理父亲关系,对于每组询问,先倍增往上跳到u能跳的最远祖先anc,

答案就是anc为根的子树的叶子结点的从大到小第k,

dfs序维护时间戳+主席树询问区间第k大,维护一下即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
const int N=2e5+10,M=5e5+10;
int h[N],x[N],sz;
int n,m,q,u,v,w,k,par[N],tot;
int in[N],out[N],dfn;
int tr[N][2],a[N],fa[N][20];//节点的两个儿子tr[0]和tr[1],节点的权值a,倍增fa数组
struct zx_tree{
	int ls[N*50],rs[N*50],rt[N*50],tot=0,sum[N*50];
	inline void upd(int &root,int pre,int l,int r,int pos){
		root=++tot;ls[root]=ls[pre],rs[root]=rs[pre],sum[root]=sum[pre]+1;
		if(l==r)return ;
		int mid=(l+r)/2;
		if(pos<=mid)upd(ls[root],ls[pre],l,mid,pos);
		else upd(rs[root],rs[pre],mid+1,r,pos);
	}
    inline int query(int root,int pre,int l,int r,int k){//区间第k小
        if(l==r)return l;
        int mid=(l+r)/2;int num=sum[ls[root]]-sum[ls[pre]];
        if(k<=num)return query(ls[root],ls[pre],l,mid,k);
        else return query(rs[root],rs[pre],mid+1,r,k-num);
    }
}T;
struct edge{
    int u,v,w;
    bool operator<(const edge &x){
        return w<x.w;
    }
}e[M];
int find(int x){
    return par[x]==x?x:par[x]=find(par[x]);
}
void merge(int x,int y,int w){
    x=find(x),y=find(y);
    if(x==y)return;
    ++tot;//建虚点tot,tot是x和y的父亲 权值设为其间权值w
    par[x]=par[y]=fa[x][0]=fa[y][0]=tot;
    tr[tot][0]=x;tr[tot][1]=y;
    a[tot]=w;
}
void Kruskal(){
    sort(e+1,e+m+1);
    tot=n;//从n+1开始建虚点tot
    for(int i=1;i<=m;++i){
        u=find(e[i].u),v=find(e[i].v),w=e[i].w;
        merge(u,v,w);
        if(tot==2*n-1){
            break;
        }
    }
}
void init(int n){
    dfn=sz=0;
    fa[0][0]=0;
    for(int i=1;i<=2*n;++i){
        par[i]=i;
        in[i]=out[i]=0;
        fa[i][0]=0;
        a[i]=0;
        tr[i][0]=tr[i][1]=0;
    }
}
void discrete(int n){
    for(int i=1;i<=n;++i){
        scanf("%d",&h[i]);
        x[++sz]=h[i];
    }
    sort(x+1,x+sz+1);
    sz=unique(x+1,x+sz+1)-(x+1);
    for(int i=1;i<=n;++i){
        h[i]=lower_bound(x+1,x+sz+1,h[i])-x;
    }
}
void dfs(int u){
    for(int i=1;(1<<i)<=n;++i){
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    in[u]=++dfn;
    if(u<=n){
        T.upd(T.rt[dfn],T.rt[dfn-1],1,sz,h[u]);
    }
    else{
        T.rt[dfn]=T.rt[dfn-1];
    }
    for(int i=0;i<2;++i){
        int v=tr[u][i];
        if(v){
            dfs(v);
        }
    }
    out[u]=dfn;
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    //两两合并 最多多建n个虚点
    init(n);
    discrete(n);
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        e[i]={u,v,w};
    }
    Kruskal();
    for(int i=1;i<=tot;++i){
        if(find(i)!=i)continue;
        dfs(i);
    }
    while(q--){
        scanf("%d%d%d",&u,&w,&k);
        for(int i=18;i>=0;--i){
            if(fa[u][i] && a[fa[u][i]]<=w){
                u=fa[u][i];
            }
        }
        int L=T.rt[in[u]-1],R=T.rt[out[u]];
        int num=T.sum[R]-T.sum[L],ans=-1;
        if(num>=k){
            ans=x[T.query(R,L,1,sz,num+1-k)];
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章