Luogu_P3302 [SDOI2013]森林【题解】主席树 lca 启发式合并

Luogu_P3302 [SDOI2013]森林

### 主席树,启发式合并,lca

luogu题面
求树上路径的第k大,树之间还有合并。
明显是主席树再加合并。

先说链上第k大,其实就是\(Tx+Ty-Tlca-Tlcafa\)
\(T\)表示权值线段树。
主席树维护的是从根节点到当前节点的前缀和。
ask的代码如下:

inline int ask(int x,int y,int lcc,int lcf,int l,int r,int k){
    if(l==r) return b[l];
    int lz=sum(lc(x))+sum(lc(y))-sum(lc(lcc))-sum(lc(lcf));
    int mid=(l+r)>>1;
    if(k<=lz) return ask(lc(x),lc(y),lc(lcc),lc(lcf),l,mid,k);
    else return ask(rc(x),rc(y),rc(lcc),rc(lcf),mid+1,r,k-lz);
}

然后就是合并了。
合并就是启发式合并。小的向大的合并。
在dfs的过程中合并,更新线段树。
dfs代码:

void dfs(int x,int ft,int rrt){
    fa[x][0]=ft;
    for(int i=1;i<=tt;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    dep[x]=dep[ft]+1;vis[x]=1;f[x]=ft;siz[rrt]++;
    int k=lower_bound(b+1,b+1+cnt,a[x])-b;
    rt[x]=insert(rt[x],rt[ft],1,cnt,k);
    for(int i=head[x];i;i=nxt(i)){
        if(to(i)==ft) continue;
        dfs(to(i),x,rrt);
    }
}

那么整道题就几乎解决了,还有一些细节自己注意。
代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+10;
int fe,n,m,q,tt,a[maxn],b[maxn],head[maxn],tot,fa[maxn][32],ans,cnt;
int dep[maxn],rt[maxn],vis[maxn],f[maxn],zho,siz[maxn];
struct node{
    int nxt,to;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
}e[maxn*4];
struct tree{
    int lc,rc,sum;
    #define sum(x) t[x].sum
    #define lc(x) t[x].lc
    #define rc(x) t[x].rc
}t[maxn*600];
inline void add(int from,int to){to(++tot)=to;nxt(tot)=head[from];head[from]=tot;}
inline int find(int x){return f[x]==x ? f[x] : f[x]=find(f[x]) ;}
inline int insert(int p,int pr,int l,int r,int k){
    if(!p) p=++zho;
    sum(p)=sum(pr)+1;
    if(l==r) return p;
    int mid=(l+r)>>1;
    if(k<=mid) lc(p)=insert(lc(p),lc(pr),l,mid,k),rc(p)=rc(pr);
    else rc(p)=insert(rc(p),rc(pr),mid+1,r,k),lc(p)=lc(pr);
    return p;
}
inline int ask(int x,int y,int lcc,int lcf,int l,int r,int k){
    if(l==r) return b[l];
    int lz=sum(lc(x))+sum(lc(y))-sum(lc(lcc))-sum(lc(lcf));
    int mid=(l+r)>>1;
    if(k<=lz) return ask(lc(x),lc(y),lc(lcc),lc(lcf),l,mid,k);
    else return ask(rc(x),rc(y),rc(lcc),rc(lcf),mid+1,r,k-lz);
}
void dfs(int x,int ft,int rrt){
    fa[x][0]=ft;
    for(int i=1;i<=tt;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    dep[x]=dep[ft]+1;vis[x]=1;f[x]=ft;siz[rrt]++;
    int k=lower_bound(b+1,b+1+cnt,a[x])-b;
    rt[x]=insert(rt[x],rt[ft],1,cnt,k);
    for(int i=head[x];i;i=nxt(i)){
        if(to(i)==ft) continue;
        dfs(to(i),x,rrt);
    }
}
inline int lca(int x,int y){
    if(dep[y]>dep[x]) swap(x,y);
    for(int i=tt;i>=0;i--)
        if(dep[fa[x][i]]>=dep[y]) 
            x=fa[x][i];
    if(x==y) return x;
    for(int i=tt;i>=0;i--)
        if(fa[x][i]!=fa[y][i]) 
            x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
signed main(){
    scanf("%lld%lld%lld%lld",&fe,&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i];
    sort(b+1,b+1+n);
    cnt=unique(b+1,b+1+n)-b-1;
    for(int u,v,i=1;i<=m;i++) scanf("%lld%lld",&u,&v),add(u,v),add(v,u);
    tt=(int)log2(n)+1;
    for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0,i),f[i]=i;
    while(q--){
        char s[10];int x,y;scanf("%s%lld%lld",s,&x,&y);
        if(s[0]=='Q'){
            int z;scanf("%lld",&z);x^=ans,y^=ans,z^=ans;
            int lc=lca(x,y);ans=ask(rt[x],rt[y],rt[lc],rt[fa[lc][0]],1,cnt,z);
            printf("%lld\n",ans);
        }
        else{
            x^=ans,y^=ans;add(x,y);add(y,x);
            int xx=find(x),yy=find(y);
            if(siz[xx]<siz[yy]) dfs(x,y,yy);
            else dfs(y,x,xx);
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/ChrisKKK/p/11600725.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值