洛谷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;
}

 

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