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

时间: 2024-10-07 20:54:32

Luogu_P3302 [SDOI2013]森林【题解】主席树 lca 启发式合并的相关文章

Count on a tree SPOJ 主席树+LCA(树链剖分实现)(两种存图方式)

Count on a tree SPOJ 主席树+LCA(树链剖分实现)(两种存图方式) 题外话,这是我第40篇随笔,纪念一下.<( ̄︶ ̄)[GO!] 题意 是说有棵树,每个节点上都有一个值,然后让你求从一个节点到另一个节点的最短路上第k小的值是多少. 解题思路 看到这个题一想以为是树链剖分+主席树,后来写着写着发现不对,因为树链剖分我们分成了一小段一小段,这些小段不能合并起来求第k小,所以这个想法不对.奈何不会做,查了查题解,需要用LCA(最近公共祖先),然后根据主席树具有区间加减的性质,我们

BZOJ 2754 SCOI2012 喵星球上的点名 fail树+set启发式合并

题目大意:给定n个目标串和m个模式串,问这m个模式串每个在多少个目标串中出现过,以及n个目标串每个以最多多少个模式串为子串 我错了--就算用fail树+set启发式合并也优化不到O(nlog^2n)--这题的数据范围相当无解啊 首先将所有名字和点名的字符串全都插进AC自动机 将每个点上开一个set记录这个点是哪些喵星人的名字的前缀 然后建立fail树 沿着fail树从下到上启发式合并 每合并完一个点 如果这个点是某次点名的字符串 那么这次点名点到的喵星人就是这个点的set中的所有元素 统计答案即

BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并

题目大意:给定一棵森林,每个点有权值,提供两种操作: 1.查询两点间路径上第k小的权值 2.将两个点之间连一条边 保证连接后仍是一座森林 可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作 对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的 大小我用了并查集 其实记录根就可以了 此外本题的多组数据是在逗比 记住testcase恒等于1就是了 NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.0 样

SPOJ COT Count on a tree(树上主席树 + LCA 求路径第k小)题解

题意:n个点的树,每个点有权值,问你u~v路径第k小的点的权值是? 思路: 树上主席树就是每个点建一棵权值线段树,具体看JQ博客,LCA用倍增logn求出,具体原理看这里 树上主席树我每个点的存的是点u到源点1的权值线段树,那我求点u到v的所有点,显然是 u + v - lca - fa[lca],就是u到1 + v到1 - 多算的lca - 多算的fa[lca].不能减去两个lca不然少一个点了, LCA板子: //LCA int fa[maxn][20]; int dep[maxn]; vo

BZOJ 2588: Spoj 10628. Count on a tree 主席树+lca

2588: Spoj 10628. Count on a tree Description 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文. Input 第一行两个整数N,M. 第二行有N个整数,其中第i个整数表示点i的权值. 后面N-1行每行两个整数(x,y),表示点x到点y有一条边. 最后M行每行两个整数(u,v,k),表示一组询问.

【BZOJ2588】Spoj 10628. Count on a tree 主席树+LCA

[BZOJ2588]Spoj 10628. Count on a tree Description 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文. Input 第一行两个整数N,M. 第二行有N个整数,其中第i个整数表示点i的权值. 后面N-1行每行两个整数(x,y),表示点x到点y有一条边. 最后M行每行两个整数(u,v,k),表示一组

【BZOJ4448】[Scoi2015]情报传递 主席树+LCA

[BZOJ4448][Scoi2015]情报传递 Description 奈特公司是一个巨大的情报公司,它有着庞大的情报网络.情报网络中共有n名情报员.每名情报员能有若干名(可能没有)下线,除1名大头日外其余n-1名情报员有且仅有1名上线.奈特公司纪律森严,每名情报员只能与自己的上.下线联系,同时,情报网络中仟意两名情报员一定能够通过情报网络传递情报. 奈特公司每天会派发以下两种任务中的一个任务: 1.搜集情报:指派T号情报员搜集情报 2.传递情报:将一条情报从X号情报员传递给Y号情报员 情报员

BZOJ3123:[SDOI2013]森林——题解

http://www.lydsy.com/JudgeOnline/problem.php?id=3123 https://www.luogu.org/problemnew/show/P3302 树上主席树操作方法看:http://www.cnblogs.com/luyouqi233/p/8159528.html (BZOJ2588:Count on a tree) 这题要动态树,显然不可能LCT套主席树啊. 那我们完全可以启发式合并一下主席树. 剩下的操作就很简单了. (然而我debug两个小时

2588. Count on a tree【主席树+LCA】

Description 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始为0,即第一个询问的u是明文. Input 第一行两个整数N,M. 第二行有N个整数,其中第i个整数表示点i的权值. 后面N-1行每行两个整数(x,y),表示点x到点y有一条边. 最后M行每行两个整数(u,v,k),表示一组询问. Output M行,表示每个询问的答案.最后一个询问不输出换行符 S