主席树

恩接下来就是主席树

高级数据结构

然而还是只会敲模板啊啊啊啊

模板题

这个,无修改的,静态,主席树,模板题。看懂花了好长时间,会敲也是差不多看一眼敲一下的,再能调出来,那我还是很棒棒

首先一个离散化,这个是很自然能想到的。因为要学主席树,首先要了解权值线段树这个概念。

权值线段树
按值域建树,打个比方:25 78 22 68 93 58 32(我随便乱打的啦♪(^∇^*)
那么这边根节点的区域范围就是:[1,93]
然后往下递归就跟线段树一样的
然后它权值线段树每个节点存的是值在当前区间范围内的数的个数。

好啦,现在了解了权值线段树了。主席树的做法就是,先把原数列中的每一个数都建一条链,加入到主席树中,然后还要连子节点。差不多就这样啦,反正我真的是说不清楚

附上模板

#include<bits/stdc++.h>
using namespace std;
long long n,m,x,y,k,t,b[100010],f[100010];
struct tr
{
    long long sum,l,r;
}
tr[10000010];
struct alsh
{
    long long val,id,flag;
}
a[100010];
bool mycmp(alsh aa,alsh bb)
{
    return aa.val<bb.val;
}
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
void build(long long &root,long long last,long long l,long long r,long long sum)
{
    root=++t;
    tr[root].sum=tr[last].sum+1;
    tr[root].l=tr[last].l;
    tr[root].r=tr[last].r;
    if (l==r) return;
    long long mid=(l+r)/2;
    if (sum<=mid) build(tr[root].l,tr[last].l,l,mid,sum);
    else build(tr[root].r,tr[last].r,mid+1,r,sum);
}
long long find(long long first,long long last,long long l,long long r,long long sum)
{
    if (l==r) return l;
    long long mid=(l+r)/2;
    long long tot=tr[tr[last].l].sum-tr[tr[first].l].sum;
    if (sum<=tot) return find(tr[first].l,tr[last].l,l,mid,sum);
    else return find(tr[first].r,tr[last].r,mid+1,r,sum-tot);
}
int main()
{
    n=read();
    m=read();
    for (int i=1;i<=n;++i)
    {
        a[i].val=read();
        a[i].id=i;
    }
    sort(a+1,a+n+1,mycmp);
    for (int i=1;i<=n;++i)
    {
        a[a[i].id].flag=++t;
        b[t]=a[i].val;
        while (a[i+1].val==a[i].val)
        {
            a[a[i+1].id].flag=t;
            i++;
        }
    }
    for (int i=1;i<=n;++i)
    build(f[i],f[i-1],1,n,a[i].flag);
    for (int i=1;i<=m;++i)
    {
        x=read();
        y=read();
        k=read();
        printf("%lld\n",b[find(f[x-1],f[y],1,n,k)]);
    }
    return 0;
}

一道福利题

Q k l r 查询数列在第k个版本时,区间[l, r]上的最大值
M k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本
最开始会给你一个数列,作为第1个版本。

#include<bits/stdc++.h>
using namespace std;
long long n,q,k,x,y,t=1,temp=1,a[100010],f[100010];
struct tr
{
    long long maxx,l,r;
}
tr[10000010];
inline int read()
{
    int num=0,flag=1;
    char c=getchar();
    for (;c<'0'||c>'9';c=getchar())
    if (c=='-') flag=-1;
    for (;c>='0'&&c<='9';c=getchar())
    num=(num<<3)+(num<<1)+c-48;
    return num*flag;
}
void build(long long l,long long r,long long o)
{
    if (l==r)
    {
        tr[o].maxx=a[l];
        return;
    }
    long long mid=(l+r)/2;
    tr[o].l=++t;
    build(l,mid,t);
    tr[o].r=++t;
    build(mid+1,r,t);
    tr[o].maxx=max(tr[tr[o].l].maxx,tr[tr[o].r].maxx);
}
void change(long long &root,long long last,long long l,long long r,long long sum)
{
    root=++t;
    tr[root].l=tr[last].l;
    tr[root].r=tr[last].r;
    if (l==r)
    {
        tr[root].maxx=y;
        return;
    }
    long long mid=(l+r)/2;
    if (sum<=mid) change(tr[root].l,tr[last].l,l,mid,sum);
    else change(tr[root].r,tr[last].r,mid+1,r,sum);
    tr[root].maxx=max(tr[tr[root].l].maxx,tr[tr[root].r].maxx);
}
long long find(long long l,long long r,long long o)
{
    if (x>r||y<l) return -1;
    if (x<=l&&y>=r) return tr[o].maxx;
    long long mid=(l+r)/2;
    long long tl=find(l,mid,tr[o].l);
    long long trr=find(mid+1,r,tr[o].r);
    return max(tl,trr);
}
int main()
{
    n=read();
    q=read();
    for (int i=1;i<=n;++i)
    a[i]=read();
    build(1,n,1);
    f[1]=1;
    for (int i=1;i<=q;++i)
    {
        int d=read();
        if (d==1)
        {
            k=read();
            x=read();
            y=read();
            change(f[++temp],f[k],1,n,x);
        }
        if (d==0)
        {
            k=read();
            x=read();
            y=read();
            printf("%lld\n",find(1,n,f[k]));
        }
    }
    return 0;
}

恩这道题确实福利,既有线段树的思想又有主席树的思想,所以我觉得海星。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值