主席树详解 zoj 2112 (单点更新)+ poj 2104 区间第k大

解决问题:单点更新,区间询问第k大的数是多少
缺点:容易MLE
复杂度:n*logn*logS (n个数,离散化后有S个不同的数)
1.树状数组套线段树:树状数组表示区间,理论上树状数组上每个元素对应一棵线段树,线段树rank区间在[l,r]的数已经有多少个,所有的线段树的结构都是一样的
2.离线操作,离散化,排序:离散化知道整个操作过程要有哪些数,排序后知道他们的rank是多少,rank值相当于一张表,当要在x位置改成u,在树状数组x,x+=x&(-x).....位置上更新线段树上rank[u]的位置,要查询[l,r]上第k小的数,得到此时第k小的数的rank,查rank表,知道这个数是多少
3.保持历史版本:由于每棵树的结构一样,所以可以共用节点。理论上有n棵线段树,但实际最开始为0,每次更新根据需要在原有的树上加入新节点
4.查询具有f[l,r]=f[1,r]-f[1,l-1]的性质
zoj 2112
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 50050
#define MAXM 10500

class CMT
{
private:
    struct Node
    {
        int sz;
        Node *ch[2];
        Node()
        {
            sz=0;ch[0]=ch[1]=NULL;
        }
        Node(int _sz,Node *ll,Node *rr)
        {
            sz=_sz;ch[0]=ll;ch[1]=rr;
        }
        int gets(){ return this?sz:0;} // 如果为空就返回0
        int getls() { return this?ch[0]->gets():0;}
        int getrs() { return this?ch[1]->gets():0;}
    }pool[(MAXN+MAXM)*40],*a[MAXN],*b[MAXN],*c[40],*d[40]; // pool是总共节点数,a,b是n个数,
    int used,amount,left,right;
//原来有n个数,避免一个个插入时n*logn*logS的复杂度,直接插入n*logS,在a数组上操作
//之后单点更新在b数组上操作,n*logn*logS,b是树状数组模式的操作,b上每个元素对应一棵线段树的根
    Node *malloc(Node *root,int w)
    {
        //if(pool[used]==NULL)
            //pool[used]=new Node(); //new出来后放在内存池,避免TLE
        Node *now=&pool[used++];
        if(root==NULL)//b数组操作时
        {
            now->sz=w;
            now->ch[0]=NULL;//每次复用时要初始化
            now->ch[1]=NULL;
        }
        else//a数组操作时
        {
            now->sz=root->sz+w;
            now->ch[0]=root->ch[0];
            now->ch[1]=root->ch[1];
        }
        return now;
    }
    Node *insert(Node *root,int x,int w,int l,int r)//a数组上的插入
    {
        Node *now=malloc(root,w);
        if(l!=r)
        {
            if(x<=(l+r)/2) now->ch[0]=insert(now->ch[0],x,w,l,l+r>>1);
            else now->ch[1]=insert(now->ch[1],x,w,(l+r>>1)+1,r);
        }
        return now;
    }
    Node *change(Node *root,int x,int w,int l,int r)//b数组上的单点更新
    {
        if(root==NULL) root=malloc(NULL,0);
        if(l!=r)
        {
            if(x<=(l+r>>1)) root->ch[0]=change(root->ch[0],x,w,l,l+r>>1);
            else root->ch[1]=change(root->ch[1],x,w,(l+r>>1)+1,r);
        }
        root->sz+=w;
        return root;
    }
    int query(int k,int lc,int ld,int l,int r)
    {
        if(l==r) return l;
        int sum=0;
        for(int i=0;i<lc;++i)
            if(c[i])
            sum-=c[i]->getls();
        for(int i=0;i<ld;++i)
            if(d[i])
            sum+=d[i]->getls();
        if(k<=sum)
        {
            for(int i=0;i<lc;++i)
                if(c[i])
                c[i]=c[i]->ch[0];
            for(int i=0;i<ld;++i)
                if(d[i])
                d[i]=d[i]->ch[0];
            return query(k,lc,ld,l,l+r>>1);
        }
        else // 如果k大于在左儿子上的个数,说明要找的数在右儿子上
        {
            for(int i=0;i<lc;++i)
                if(c[i])
                c[i]=c[i]->ch[1];
            for(int i=0;i<ld;++i)
                if(d[i])
                d[i]=d[i]->ch[1];
            return query(k-sum,lc,ld,(l+r>>1)+1,r);
        }
    }

public:
    void clear(int n,int l,int r)//l,r是整棵线段树的左右区间,根据全部数排序时是从0还是1开始排,决定l是0还是1
    {
        used=0;
        amount=n;
        left=l;
        right=r;
        for(int i=0;i<=n;++i)// a,b都是1-n的
            a[i]=b[i]=NULL;
    }
    void insert(int i,int x,int w)
    {
        a[i]=insert(a[i-1],x,w,left,right);//a[i]是1-i的叠加
    }
    int query(int l,int r,int k)//要询问[l+1,r]的结果,即f(r)-f(l)
    {
        int lc=0,ld=0;
        c[lc++]=a[l];
        d[ld++]=a[r];
        for(int x=l;x;x-=x&(-x))
            c[lc++]=b[x];
        for(int x=r;x;x-=x&(-x))
            d[ld++]=b[x];
        return query(k,lc,ld,left,right);
    }
    void change(int i,int x,int w)
    {
        while(i<=amount)
        {
            b[i]=change(b[i],x,w,left,right);
            i+=i&(-i);
        }
    }
}cmt;

struct CMD
{
    int x,y,k;
    char ch;
}cmd[MAXM];
int a[MAXN],b[MAXM+MAXN],tot,n,m;

int main ()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        tot=0;
        for(int i=0;i<n;++i)
        {
            scanf("%d",&a[i]);
            b[tot++]=a[i];
        }
        for(int i=0;i<m;++i)
        {
            scanf(" %c",&cmd[i].ch);
            if(cmd[i].ch=='Q')
                scanf("%d%d%d",&cmd[i].x,&cmd[i].y,&cmd[i].k);
            else {
                    scanf("%d%d",&cmd[i].x,&cmd[i].k);
                b[tot++]=cmd[i].k;
            }
        }
        sort(b,b+tot);
        int cnt=1;
        for(int i=1;i<tot;++i)
            if(b[i]!=b[i-1])
            b[cnt++]=b[i];
        tot=cnt;

        cmt.clear(n,0,tot-1);
        for(int i=0;i<n;++i)
        {
            int j=lower_bound(b,b+tot,a[i])-b;
            cmt.insert(i+1,j,1);// i+1是a数组上更新的位置,j是对应的线段树更新的位置
        }
        for(int i=0;i<m;++i)
        {
            if(cmd[i].ch=='Q')
            {
                printf("%d\n",b[cmt.query(cmd[i].x-1,cmd[i].y,cmd[i].k)]); //f[l,r]=f[1,r]-f[1,l-1]
            }
            else
            {
                int j=lower_bound(b,b+tot,a[cmd[i].x-1])-b;
                cmt.change(cmd[i].x,j,-1);
                a[cmd[i].x-1]=cmd[i].k;
                j=lower_bound(b,b+tot,a[cmd[i].x-1])-b;
                cmt.change(cmd[i].x,j,1);
            }
        }
    }
    return 0;
}



poj2104
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 105050
#define MAXM 10050

class CMT
{
private:
    struct Node
    {
        int sz;
        Node *ch[2];
        int gets(){ return this?sz:0;} // 如果为空就返回0
        int getls() { return this?ch[0]->gets():0;}
        int getrs() { return this?ch[1]->gets():0;}
    }pool[MAXN*50],*a[MAXN];// pool是总共节点数,a,b是n个数,
    int used,amount,left,right;
//原来有n个数,避免一个个插入时n*logn*logS的复杂度,直接插入n*logS,在a数组上操作
//之后单点更新在b数组上操作,n*logn*logS,b是树状数组模式的操作,b上每个元素对应一棵线段树的根
    Node *malloc(Node *root,int w)
    {
        Node *now=&pool[used++];
        if(root==NULL)//b数组操作时
        {
            now->sz=w;
            now->ch[0]=NULL;//每次复用时要初始化
            now->ch[1]=NULL;
        }
        else//a数组操作时
        {
            now->sz=root->sz+w;
            now->ch[0]=root->ch[0];
            now->ch[1]=root->ch[1];
        }
        return now;
    }
    Node *insert(Node *root,int x,int w,int l,int r)//a数组上的插入
    {
        Node *now=malloc(root,w);
        if(l!=r)
        {
            if(x<=(l+r)/2) now->ch[0]=insert(now->ch[0],x,w,l,l+r>>1);
            else now->ch[1]=insert(now->ch[1],x,w,(l+r>>1)+1,r);
        }
        return now;
    }
    int query(Node *lq,Node *rq,int k,int l,int r)
    {
        if(l==r) return l;
        int sum=rq->getls()-lq->getls();
        if(k<=sum)
        {
            if(lq) lq=lq->ch[0];
            if(rq) rq=rq->ch[0];
            return query(lq,rq,k,l,l+r>>1);
        }
        else
        {
            if(lq) lq=lq->ch[1];
            if(rq) rq=rq->ch[1];
            return query(lq,rq,k-sum,(l+r>>1)+1,r);
        }
    }

public:
    void clear(int n,int l,int r)//l,r是整棵线段树的左右区间,根据全部数排序时是从0还是1开始排,决定l是0还是1
    {
        used=0;
        amount=n;
        left=l;
        right=r;
        for(int i=0;i<=n;++i)// a,b都是1-n的
            a[i]=NULL;
    }
    void insert(int i,int x,int w)
    {
        a[i]=insert(a[i-1],x,w,left,right);//a[i]是1-i的叠加
    }
    int query(int l,int r,int k)
    {
        return query(a[l],a[r],k,left,right);
    }

}cmt;

struct CMD
{
    int x,y,k;
}cmd[MAXM];
int a[MAXN],b[MAXN],tot,n,m;

int main ()
{
        scanf("%d%d",&n,&m);
        tot=0;
        for(int i=0;i<n;++i)
        {
            scanf("%d",&a[i]);
            b[++tot]=a[i];
        }
        for(int i=0;i<m;++i)
        {
            scanf("%d%d%d",&cmd[i].x,&cmd[i].y,&cmd[i].k);
        }
        sort(b+1,b+1+tot);
        int cnt=1;
        for(int i=2;i<=tot;++i)
            if(b[i]!=b[i-1])
            b[++cnt]=b[i];
        tot=cnt;

        cmt.clear(n,1,tot);
        for(int i=0;i<n;++i)
        {
            int j=lower_bound(b+1,b+1+tot,a[i])-b;
            cmt.insert(i+1,j,1);// i+1是a数组上更新的位置,j是对应的线段树更新的位置
        }
        for(int i=0;i<m;++i)
        {
                printf("%d\n",b[cmt.query(cmd[i].x-1,cmd[i].y,cmd[i].k)]); //f[l,r]=f[1,r]-f[1,l-1]
        }

    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值