【8.12】权值线段树 & 【8.20】主席树

权值线段树

参考blog

与普通线段树的区别

  • 普通线段树维护数列的区间信息
  • 权值线段树维护的区间信息

\(T[x]\) 为数列 \(a\)\(x\) 出现的个数。

权值线段树维护的是桶 \(T\)

建树/插入/查询

同普通线段树。

求整个数列的第 \(K\) 大/小值

假设 \(K=5\) ,查询第 \(K\) 大。

首先从根节点出发,由于要查询第 \(K\) 大,是相对于终点而言的,因此从右子节点开始判断:

当前节点右子树包含 \(4\) 个元素,所以应该向左子树遍历,注意:此时应该减去右子树的 \(4\) 个元素!

寻找第 \(K\) 小的操作与上方类似,区别在于相对于起点OR终点而言(遍历时对左右子树的判断顺序)。

如果元素值域较大导致空间不足,应使用离散化。

主席树

参考blog (讲解清晰)

参考blog (代码清晰)

主席树就是利用函数式编程的思想来使线段树支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗的增强版的线段树。

说白了,也就是可持久化线段树。

例如,对于序列 \(a=1,4,3\) ,分别对序列 \([a_1],[a_1,a_2],[a_1,a_2,a_3]\) 暴力建 \(n\) 个权值线段树。如下图。

这样我们就可以利用前缀和的性质求出对于任意区间 \([l,r]\)\(x\) 在区间内出现的次数。

但无论是时间还是空间,这样暴力建树都是不可接受的。因此就有了主席树。

主席树要求除被修改的链外,其他没有被修改的点与上一颗树上的历史状态相连。如下图。

显然,这个图能把后面一颗树重复的东西在前面几颗树里找到。

例题

[POI2014] KUR-Couriers

题面翻译

给一个长度为 \(n\) 的正整数序列 \(a\)。共有 \(m\) 组询问,每次询问一个区间 \([l,r]\) ,是否存在一个数在 \([l,r]\) 中出现的次数严格大于一半。如果存在,输出这个数,否则输出 \(0\)

\(1 \leq n,m \leq 5 \times 10^5\)\(1 \leq a_i \leq n\)

Solution

使用主席树维护区间 \([1,R(1 \leq R \leq n)]\) 中每个数出现的次数,利用前缀和即可求出在任意区间 \([L,R]\) 中每个数出现的次数,进而得到是否有数满足要求。对于此题需稍加剪枝。

Code

#include <bits/stdc++.h>
#define MAXN 500003
using namespace std;
/* FIO */
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
        x=x*10+ch-'0',ch=getchar();
    return x*f;
}
void write(int x)
{
    if(x<0)
        putchar('-'),x=-x;
    if(x>9)
        write(x/10);
    putchar(x%10+'0');
    return;
}
/* FIO */
int n,m;
int a[MAXN];
int tot=0; int root[MAXN];
int lson[MAXN<<5],rson[MAXN<<5],sum[MAXN<<5];
int build(int l,int r){
    int rt=++tot;
    sum[rt]=0;
    if (l<r){
        int mid=(l+r)>>1;
        lson[rt]=build(l,mid);
        rson[rt]=build(mid+1,r);
    }
    return rt;
}
int update(int p,int l,int r,int x){
    int rt=++tot;
    sum[rt]=sum[p]+1;
    if (l<r){
        int mid=(l+r)>>1;
        if (x>mid) {lson[rt]=lson[p]; rson[rt]=update(rson[p],mid+1,r,x);} //此处x的分界一定要与后面query()
        else       {rson[rt]=rson[p]; lson[rt]=update(lson[p],l,mid,x);}   //中的l,mid&mid+1,r的分界相同
    }                                                                      //以保证修改路径与查询路径一致
    return rt;
}
struct RES{
    int val;
    int num;
};
RES query(int u,int v,int l,int r,int x){
    if (l==r){
        return (RES){sum[v]-sum[u],l};
    }
    int mid=(l+r)>>1;
    RES Lres={0,0},Rres={0,0};
    if (2*(sum[lson[v]]-sum[lson[u]])>x) Lres=query(lson[u],lson[v],l,mid,x);
    if (2*(sum[rson[v]]-sum[rson[u]])>x) Rres=query(rson[u],rson[v],mid+1,r,x);
    if (Lres.val>Rres.val) return Lres;
    else                   return Rres;
}
int main(){
    n=read(); m=read();
    root[0]=build(1,n);
    for (int i=1;i<=n;i++) a[i]=read();
    for (int i=1;i<=n;i++){
        root[i]=update(root[i-1],1,n,a[i]);
    }
    for (int i=1;i<=m;i++){
        int l,r;
        l=read(); r=read();
        RES res=query(root[l-1],root[r],1,n,r-l+1);
        write(res.num);
        putchar('\n');
    }
    return 0;
}

P2633 Count on a tree

树上主席树板子

Code

#include <bits/stdc++.h>
#define MAXN 100003
#define inf 0x3f3f3f3f
#define mod 998244353
#define pb push_back
using namespace std;
template<class T>void err(const T &x,const char c='\n') {cerr<<x<<c;}
template<class T,class ...Args>void err(const T &x,const Args &...args) {err(x,' '),err(args...);}
bool ST;
int n,q;
struct NUM{
    int num;
    int id;
    bool operator<(const NUM &B)const{
        return num<B.num;
    }
}a[MAXN];
int b[MAXN];
int to[MAXN];
int ROOT=1;
vector <int> e[MAXN]; int fa[MAXN][23]; int dep[MAXN];
int lca(int u,int v){
    if (dep[u]<dep[v]) swap(u,v);
    int cha=dep[u]-dep[v];
    for (int i=20;i>=0;i--){
        if (cha&(1<<i)) u=fa[u][i];
    }
    if (u==v) return u;
    for (int i=20;i>=0;i--){
        if (fa[u][i]!=fa[v][i]){
            u=fa[u][i]; v=fa[v][i];
        }
    }
    return fa[u][0];
}
int root[MAXN],cnt;
int tree[MAXN<<5],ls[MAXN<<5],rs[MAXN<<5];
void build(int tr,int l,int r){
    if (l==r){
        return;
    }
    int mid=(l+r)>>1;
    ls[tr]=++cnt; rs[tr]=++cnt;
    build(ls[tr],l,mid);
    build(rs[tr],mid+1,r);
    return;
}
void insert(int ytr,int tr,int l,int r,int x){
    tree[tr]=tree[ytr]+1;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) {rs[tr]=rs[ytr]; ls[tr]=++cnt; insert(ls[ytr],ls[tr],l,mid,x);}
    else {ls[tr]=ls[ytr]; rs[tr]=++cnt; insert(rs[ytr],rs[tr],mid+1,r,x);}
    return;
}
int last;
void query(int trlca,int trfalca,int tru,int trv,int l,int r,int k){
    if (l==r){
        cout<<to[l]<<'\n';
        last=to[l];
        return;
    }
    int mid=(l+r)>>1;
    if (tree[ls[tru]]+tree[ls[trv]]-tree[ls[trlca]]-tree[ls[trfalca]]>=k) query(ls[trlca],ls[trfalca],ls[tru],ls[trv],l,mid,k);
    else {k-=tree[ls[tru]]+tree[ls[trv]]-tree[ls[trlca]]-tree[ls[trfalca]]; query(rs[trlca],rs[trfalca],rs[tru],rs[trv],mid+1,r,k);}
    return;
}
int vis[MAXN];
void dfs(int u,int f){
    vis[u]=1;
    dep[u]=dep[f]+1;
    fa[u][0]=f;
    root[u]=++cnt;
    insert(root[f],root[u],1,n,b[u]);
    for (int v:e[u]){
        if (vis[v]) continue;
        dfs(v,u);
    }
    return;
}
bool ED;
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>q;
    for (int i=1;i<=n;i++) {cin>>a[i].num; a[i].id=i;}
    sort(a+1,a+1+n);
    for (int i=1,last=-1,CNT=0;i<=n;i++){
        if (last!=a[i].num) CNT++;
        b[a[i].id]=CNT; to[CNT]=a[i].num;
        last=a[i].num;
    }
    for (int i=1;i<=n-1;i++){
        int u,v; cin>>u>>v;
        e[u].pb(v); e[v].pb(u);
    }
    root[0]=++cnt;
    build(root[0],1,n);
    dfs(ROOT,0);
    for (int i=1;i<=20;i++){
        for (int j=1;j<=n;j++){
            fa[j][i]=fa[fa[j][i-1]][i-1];
        }
    }
    while (q--){
        int u,v,k; cin>>u>>v>>k;
        u=u^last;
        int LCA=lca(u,v);
        query(root[LCA],root[fa[LCA][0]],root[u],root[v],1,n,k);
    }
    return 0;
}
posted @   EcapsXD  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示