主席树总结

PS:待修改主席树不会,树套树从来写不动

其实很早就学过这个东西了,基本的思路也可以理解,最基本的应用就是用来求区间第K大

贴一下我的主席树模板

#include<iostream>
#include<cstdio>
#include<algorithm>
#define M 200010
#define ls ch[node][0]
#define rs ch[node][1]
using namespace std;

int n,m,cnt,sz;
int a[M],b[M],rt[M],sum[M<<5],ch[M<<5][2];

int get(int x)
{
    int l=1,r=cnt;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(b[mid]==x) return mid;
        if(b[mid]>x) r=mid-1;
        else l=mid+1;
    }
}
void build(int &node,int l,int r)
{
    node=++sz;
    if(l==r) return;
    int mid=(l+r)/2;
    build(ls,l,mid),build(rs,mid+1,r);
}

void insert(int &node,int pre,int l,int r,int x)
{
    node=++sz;
    sum[node]=sum[pre]+1,ls=ch[pre][0],rs=ch[pre][1];
    if(l==r) return;
    int mid=(l+r)/2;
    if(x<=mid) insert(ls,ch[pre][0],l,mid,x);
    else insert(rs,ch[pre][1],mid+1,r,x);
}

int query(int l1,int r1,int l,int r,int k)
{
    if(l==r) return l;
    int now=sum[ch[l1][0]]-sum[ch[r1][0]],mid=(l+r)/2;
    if(k<=now) return query(ch[l1][0],ch[r1][0],l,mid,k);
    else return query(ch[l1][1],ch[r1][1],mid+1,r,k-now);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[++cnt]=a[i];
    }
    sort(b+1,b+1+n);
    cnt=unique(b+1,b+1+n)-b-1;
    build(rt[0],1,n);
    for(int i=1;i<=n;i++) 
    {
        a[i]=get(a[i]);
        rt[i]=rt[i-1];
        insert(rt[i],rt[i-1],1,cnt,a[i]);
    }
    for(int i=1;i<=m;i++)
    {
        int l,r,k;scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",b[query(rt[r],rt[l-1],1,cnt,k)]);
    }
    return 0;
}
View Code

一般来说理解了权值线段树再学主席树就很好理解了,因为权值线段树存在可减性,所以最基础的主席树就是在两棵权值线段树每个节点相减得到的新的线段树上进行操作,当然还会有别的变化,但无论怎么变这个线段树都应该是权值线段树

最近碰到了这样一个类似二维数点的题目然而我这么菜当然不会做啦

有$n$条线段,每次查询在区间[$l$,$r$]内线段的条数

首先我们把线段都按照左端点排好序,这样问题就转化成:

求所有左端点在区间[$l$,$r$]内,其对应的右端点也在区间[$l$,$r$]内的点对的个数

我们可以对每个左端点建立主席树,维护其右端点的位置,查询就是求$Tree[r]$-$Tree[l-1]$构成的权值线段树在区间[$l$,$r$]内的$size$

是不是很简单~

下面放几道我最近做的主席树的题目

1.Count on a tree

题意:求树上两点之间的点权第k大值

对于一条链$u->v$上的信息,我们可以把它看成$(u->root)+(v->root)-LCA->root-fa_LCA->root$。这样我们把每个点在它的父节点的基础上建立主席树,查询的时候就不再是两棵树相减,而是四棵树相加减了

2.middle

题意:给定一个序列,每次查询左端点在[$a$,$b$]之间,右端点在[$c$,$d$]之间的中位数最大的子串

我们考虑转化成二分和0/1序列判定,即我们应该知道的是二分到每个数时序列的0/1状态,当我们加入最小的数时,序列的0/1状态为该位置是0,其他为1,再加入第二个数时,又在第二个数的位置上多了一个0。排序后每个数的二分序列都是在上一个数的二分序列的基础上增加一个0。那么思路就很明显了,用主席树维护每次在原来的基础上加修改一个位置即可。(因为这个主席树跟上边的不是很一样,感觉说的不是很清楚,自己画画图就很明显了)

3.谈笑风生

题意:给定一棵树T,每次询问给出两个数$a$和$k$,求使得$b$和$a$距离不超过$k$,且$a,b$均为$c$的祖先的三元组的个数

首先我们分析题目可以发现,这个$c$求存在于$a,b$深度较大的那个点得子树中

先考虑$deep_a > deep_b$,那么答案即为$k*(size_a -1)$

否则,我们需要枚举每个$a$的子树内与$a$距离不超过$k$的点的$size-1$

那么这玩意我们用主席树维护一下即可,因为是子树问题,所以主席树应该按照DFS序去建立

4.mex

题意:有一个长度为n的数组{a1,a2,...,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。

一道思路很巧妙的主席树的题目,也可以用莫队做(但是我感觉莫队的转移复杂度不是很靠谱)

我们考虑一个一个数如果出现在[$l$,$r$]区间内,那么在$r$之前,该数最晚出现的位置一定大于等于$l$,用主席树维护一下每个数出现的最晚时间,对于一个大于n的数,是不可能到达的,直接忽略它就行了

转载于:https://www.cnblogs.com/Slrslr/p/9813425.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值