bzoj 1901/zoj 2112(主席树+树状数组)

传送门
题解:又一道《万年A不掉》系列。外层树状数组维护内层权值线段树,修改/查询/空间复杂度都是(O(nlog(n)^2))。关于前缀,由静态root[r]-root[l]变为两个按树状数组lowbit处理过的root池(pl,pr)相减。终于大概明白树状数组到底怎么在外层维护权值线段树orz,树状数组大法好(=゚ω゚)ノ.

P.S.如果此时n变大(zoj 2112),就需要优化空间。对于静态的建树只要nlogn个节点就可以了,而且对于修改操作,只是修改M次,每次改变俩个值(减去原先的,加上现在的)。也就是说如果把所有初值都插入到树状数组里会大幅浪费空间,所以初值按照静态来建,内存O(nlogn),而修改部分保存在树状数组中,每次修改logn棵树,每次插入增加logn个节点O(mlognlogn+nlogn)。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=1e4+4;
int n,m,a[MAXN],b[MAXN<<1];//b[]需要两倍!!! 
int tot=0,cnt=0,nn,root[MAXN],lc[MAXN*226],rc[MAXN*226],siz[MAXN*226];//MAXN*log(MAXN)^2
int cl,cr,pl[MAXN],pr[MAXN];//按lowbit存储query用到的root集合
struct Q {
    int l,r,k;
}q[MAXN];
inline int read() {
    int x=0;char c=getchar();
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}
void modify(int &rt,int l,int r,int val,int delta) {
    lc[++tot]=lc[rt],rc[tot]=rc[rt],siz[tot]=siz[rt];
    rt=tot,siz[rt]+=delta;
    if (l==r) return ;
    int mid=(l+r)>>1;
    if (val<=mid) modify(lc[rt],l,mid,val,delta);
    else modify(rc[rt],mid+1,r,val,delta);
}
inline void add(int x,int val,int delta) {
    for (;x<=n;x+=x&(-x)) modify(root[x],1,nn,val,delta);
}
int query(int l,int r,int k) {
    if (l==r) return l;
    int suml=0,sumr=0;
    for (int i=1;i<=cl;++i) suml+=siz[lc[pl[i]]];
    for (int i=1;i<=cr;++i) sumr+=siz[lc[pr[i]]];
    int mid=(l+r)>>1;
    if (k<=sumr-suml) {
        for (int i=1;i<=cl;++i) pl[i]=lc[pl[i]];
        for (int i=1;i<=cr;++i) pr[i]=lc[pr[i]];
        return query(l,mid,k);
    }
    else {
        for (int i=1;i<=cl;++i) pl[i]=rc[pl[i]];
        for (int i=1;i<=cr;++i) pr[i]=rc[pr[i]];
        return query(mid+1,r,k-sumr+suml);
    }
}
inline int ask(int l,int r,int k) {
    for (cl=0;l;l-=l&(-l)) pl[++cl]=root[l];//记录"左前缀"
    for (cr=0;r;r-=r&(-r)) pr[++cr]=root[r];//记录"右前缀"
    return query(1,nn,k);
}
int main() {
//  freopen("bzoj 1901.in","r",stdin);
    n=read(),m=read();
    for (register int i=1;i<=n;++i) b[++cnt]=a[i]=read();
    for (register int i=1;i<=m;++i) {
        char s;while (!isalpha(s=getchar()));
        if (s^'Q') q[i].l=read(),b[++cnt]=q[i].r=read(),q[i].k=0;
        else q[i].l=read(),q[i].r=read(),q[i].k=read();
    }
    sort(b+1,b+cnt+1);
    nn=unique(b+1,b+cnt+1)-b-1;//离线离散化
    for (register int i=1;i<=n;++i) add(i,a[i]=lower_bound(b+1,b+nn+1,a[i])-b,1);
    for (register int i=1;i<=m;++i) {
        if (q[i].k) printf("%d\n",b[ask(q[i].l-1,q[i].r,q[i].k)]);
        else {
            add(q[i].l,a[q[i].l],-1);
            a[q[i].l]=lower_bound(b+1,b+nn+1,q[i].r)-b;
            add(q[i].l,a[q[i].l],1);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值