树状数组套主席树

年轻人的第一次树套树

首先是一道经典的动态区间第k大。

先回想一下静态区间第k大的做法,主席树维护前缀的权值线段树,查询时在 l − 1 l-1 l1 r r r 两颗权值线段树上一起二分。
动态问题无非是添加了一个修改步骤,而如果要暴力修改的话需要对 i i i n n n 的线段树都进行修改,复杂度来到 n 2 l o g n n^2logn n2logn
考虑用树状数组维护对应区间的权值线段树,每次修改对 l o g n logn logn 级别数量的线段树进行修改,查询时在 l o g n logn logn 级别数量线段树上同时二分,因此整体复杂度 O ( n l o g n l o g n ) O(n logn logn) O(nlognlogn)

代码

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;

struct T{
    char c;
    int a,b,k;
}q[maxn];

int b[3*maxn],num[maxn];
int n,m;

int rt[maxn];
class ptree{public:
#define nd node[now]
#define ndp node[pre]
#define mid (s+t)/2
    int cnt;
    struct segnode{
        int l,r,sum;
    }node[maxn*500];
    void update(int pos, int val, int s, int t, int &now, int pre){
        if(!now) now = ++cnt;
        nd = ndp, nd.sum += val;
        if(s == t) return ;

        if(pos <= mid) update(pos, val, s, mid, nd.l, ndp.l);
        else update(pos, val, mid+1, t, nd.r, ndp.r);
    }
#undef mid
}tree1;

class bit{public:
    int lb(int x){return x&(-x);}
    void update(int pos,int x,long long val){
        if(pos>0)
            for(int i=pos;i<=n;i+=lb(i)){
                tree1.update(x,val,1,b[0],rt[i],rt[i]);
            }
    }

    int rtl[maxn],rtr[maxn];
    int query(int l,int r,int k){
        int s=1,t=b[0];
        int mid,sum;
        for(int i=l-1;i;i-=lb(i)) rtl[i]=rt[i];
        for(int i=r;i;i-=lb(i)) rtr[i]=rt[i];

        while(s<t){
            mid=(s+t)/2,sum=0;
            for(int i=l-1;i;i-=lb(i)) sum-=tree1.node[tree1.node[rtl[i]].l].sum;
            for(int i=r;i;i-=lb(i)) sum+=tree1.node[tree1.node[rtr[i]].l].sum;
            if(sum>=k){
                for(int i=l-1;i;i-=lb(i)) rtl[i]=tree1.node[rtl[i]].l;
                for(int i=r;i;i-=lb(i)) rtr[i]=tree1.node[rtr[i]].l;
                t=mid;
            }
            else{
                for(int i=l-1;i;i-=lb(i)) rtl[i]=tree1.node[rtl[i]].r;
                for(int i=r;i;i-=lb(i)) rtr[i]=tree1.node[rtr[i]].r;
                k-=sum;
                s=mid+1;
            }
        }
        return s;
    }
}tree2;

int main(){
//    ios::sync_with_stdio(false);
//    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>num[i],b[++b[0]]=num[i];
    for(int i=1;i<=m;i++){
        cin>>q[i].c;
        if(q[i].c=='Q') cin>>q[i].a>>q[i].b>>q[i].k;
        else cin>>q[i].a>>q[i].b,b[++b[0]]=q[i].b;
    }
    sort(b+1,b+1+b[0]);
    b[0]=unique(b+1,b+1+b[0])-b-1;

    for(int i=1;i<=n;i++) num[i]=lower_bound(b+1,b+1+b[0],num[i])-b;
    for(int i=1;i<=m;i++){
        if(q[i].c=='C')
            q[i].b=lower_bound(b+1,b+1+b[0],q[i].b)-b;
    }
    for(int i=1;i<=n;i++) tree2.update(i,num[i],1);
    
    for(int i=1;i<=m;i++){
        if(q[i].c=='Q'){
            int l=q[i].a, r=q[i].b, k=q[i].k;
            cout<<b[tree2.query(l,r,k)]<<endl;
        }
        else{
            int temp=q[i].a,to=q[i].b;
            tree2.update(temp,num[temp],-1);
            num[temp]=to;
            tree2.update(temp,num[temp],1);
        }
    }
}

第二题是2019南昌icpc网络赛的i题

题目大意:
给定一个序列a,支持单点修改,询问区间中值域在一定范围内的最长连续段的个数。
例如序列 3 3 1 5 6 5, 区间[1,3],值域在[1,3]范围的最长连续段个数为2,分别为 [1,2] 和 [3,3]。

思路:
首先如果去掉修改操作,主席树维护前缀值域区间的序列最长连续段个数,按下标建树即可,查询时直接查询值域 y 和值域 x-1 的区间然后相减即可。
加上修改操作,加上树状数组之后主席树按照树状数组的区间值域建树即可。具体实现查询序列最长连续段个数的方法是,每个节点维护最长连续段个数和左端点、右端点的值,两个区间如果端点值相同,合并后最长连续段个数等于两个区间的最长连续段个数和-1,否则不-1。

然而写完之后发现空间开不下。。。 等我学完cdq再来吧:)

代码先放在这里

#include<bits/stdc++.h>
using namespace std;

const int maxn=2e5+5;

int n,m;

int rt[maxn];
class ptree{public:
#define nd node[now]
#define ndp node[pre]
#define mid (l+r)/2
    int cnt;
    struct segnode{
        int l,r,sum,sl,sr;
    }node[maxn*130];

    void pushup(int now){
        if(!nd.l) nd.sum=node[nd.r].sum,nd.sr=node[nd.r].sr,nd.sl=-1;
        else if(!nd.r) nd.sum=node[nd.l].sum,nd.sl=node[nd.l].sl,nd.sr=-1;
        else{
            if(node[nd.l].sr==node[nd.r].sl) nd.sum=node[nd.l].sum+node[nd.r].sum-1;
            else nd.sum=node[nd.l].sum+node[nd.r].sum;
            nd.sl=node[nd.l].sl,nd.sr=node[nd.r].sr;
        }
    }

    void update(int pos, int x, int l, int r, int &now, int pre){
        if(!now) now = ++cnt;
        nd=ndp;
        if(l == r){
            nd.sum=1;
            nd.sl=x,nd.sr=x;
            return;
        }
        if(pos <= mid) update(pos, x, l, mid, nd.l, ndp.l);
        else update(pos, x, mid+1, r, nd.r, ndp.r);
        pushup(now);
    }

    int query(int l,int r,int s,int t,int now){
        if(s<=l && r<=t) return nd.sum;
        char ll=-1;
        int sum=0;
        if(s<=mid && nd.l!=0) ll=node[nd.l].sr,sum+=query(l,mid,s,t,nd.l);
        if(t>mid && nd.r!=0){
            if(ll==node[nd.r].sl) sum+=query(mid+1,r,s,t,nd.r)-1;
            else sum+=query(mid+1,r,s,t,nd.r);
        }
        return sum;
    }
#undef mid
}tree1;

class bit{public:
    int lb(int x){return x&(-x);}
    void update(int pos,int t){
        if(pos>0)
            for(int i=pos;i<=n;i+=lb(i)){
                tree1.update(t,pos,1,n,rt[i],rt[i]);
            }
    }
    int query(int pos,int l,int r){
        int sum=0;
        for(int i=pos;i;i-=lb(i)){
            sum+=tree1.query(1,n,l,r,rt[i]);
        }
        return sum;
    }
    int ask(int l,int r,int x,int y){
        return query(y,l,r)-query(x-1,l,r);
    }
}tree2;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        tree2.update(x,i);
    }
    for(int i=1;i<=m;i++){
        int q;
        cin>>q;
        if(q==1){
            int pos,v;
            cin>>pos>>v;
            tree2.update(v,pos);
        }
        else{
            int l,r,x,y;
            cin>>l>>r>>x>>y;
            cout<<tree2.ask(l,r,x,y)<<'\n';
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值