【zju2112/bzoj1901】Dynamic Rankings

题目链接

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1112

题目大意

带修改操作的区间第K小问题。

注意:bzoj上的数据范围 n10000 ,zju上的数据范围 n50000

题解

裸的树状数组套主席树。查询的时候二分当前答案区间 [l,r] ,如果在 [l,mid] 区间内的权值个数 sumk ,那么转移到 [l,mid] ,否则转移到 [mid+1,r] 并把 k 减去sum
时间复杂度 O(nlognlogs) ,其中 s 是权值范围,如果写了离散化那么就是O(nlog2n)
空间复杂度也是 O(nlog2n) ,貌似在zju上卡不过去。

#include <algorithm>
#include <cstdio>
const int N=10005,inf=1000000000;
int tr[N*900],ls[N*900],rs[N*900],a[N],rt[N],n,m,tot=0;
void add(int &now,int l,int r,int x,int y){
    if (!now) now=++tot;
    tr[now]+=y;
    if (l==r) return;
    int mid=(l+r)>>1;
    if (x<=mid) add(ls[now],l,mid,x,y);
        else add(rs[now],mid+1,r,x,y);
}
void insert(int x,int y,int z){
    for (;x<=n;x+=x&-x) add(rt[x],0,inf,y,z);
}
int query(int x,int y,int z){
    int a[20],b[20],ta=0,tb=0;
    for (--x;x;x-=x&-x) a[++ta]=rt[x];
    for (;y;y-=y&-y) b[++tb]=rt[y];
    int l=0,r=inf;
    for (;l<r;){
        int mid=(l+r)>>1,sum=0;
        for (int i=1;i<=ta;i++) sum-=tr[ls[a[i]]];
        for (int i=1;i<=tb;i++) sum+=tr[ls[b[i]]];
        if (z<=sum){
            r=mid;
            for (int i=1;i<=ta;i++) a[i]=ls[a[i]];
            for (int i=1;i<=tb;i++) b[i]=ls[b[i]];
        }
        else{
            z-=sum;l=mid+1;
            for (int i=1;i<=ta;i++) a[i]=rs[a[i]];
            for (int i=1;i<=tb;i++) b[i]=rs[b[i]];
        }
    }
    return l;
}
int main(){
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        insert(i,a[i],1);
    }
    scanf("\n");
    for (int i=1;i<=m;i++){
        char c;int x,y,z;
        scanf("%c%d%d",&c,&x,&y);
        if (c=='Q'){
            scanf("%d",&z);
            int ans=query(x,y,z);
            printf("%d\n",ans);
        }
        else{
            insert(x,a[x],-1);
            insert(x,y,1);
            a[x]=y;
        }
        scanf("\n");
    }
}

树状数组套平衡树,也是经典做法。查询的时候二分答案然后统计,因此时间复杂度 O(nlog3n) ,空间复杂度 O(nlogn)

#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int N=50005,inf=1000000000;
vector<int> vec;
int ch[N*15][2],v[N*15],cnt[N*15],f[N*15],a[N],rt[N],n,m,tot,cur;
int newnode(){
    if (vec.size()){
        int x=vec.back();
        vec.pop_back();
        return x;
    }
    else return ++tot;
}
bool noroot(int x){return ch[f[x]][0]==x || ch[f[x]][1]==x;}
void pushup(int x){cnt[x]=cnt[ch[x][0]]+cnt[ch[x][1]]+1;}
void rotate(int x){
    int y=f[x],tp=ch[y][1]==x;
    if (noroot(y)) ch[f[y]][ch[f[y]][1]==y]=x;
    f[x]=f[y];
    f[ch[y][tp]=ch[x][tp^1]]=y;
    f[ch[x][tp^1]=y]=x;
    pushup(y);
}
void splay(int id,int x){
    if (!x) return;
    for (;noroot(x);rotate(x))
    if (noroot(f[x])){
        if ((ch[f[x]][0]==x)^(ch[f[f[x]]][0]==f[x])) rotate(x);
            else rotate(f[x]);
    }
    pushup(x);
    rt[id]=x;
}
void add(int id,int &now,int fa,int x){
    if (!now){
        v[now=newnode()]=x;f[now]=fa;cnt[now]=1;
        splay(id,now);
    }
    else add(id,ch[now][x>v[now]],now,x);
}
void insert(int x,int y){
    for (;x<=n;x+=x&-x) add(x,rt[x],0,y);
}
void del(int id,int now,int x){
    if (v[now]==x){
        splay(id,now);
        if (!ch[now][0]) f[rt[id]=ch[now][1]]=0;
            else if (!ch[now][1]) f[rt[id]=ch[now][0]]=0;
                else{
                    int x=ch[now][0];f[x]=0;
                    for (;ch[x][1];x=ch[x][1]);
                    f[ch[x][1]=ch[now][1]]=x;
                    splay(id,ch[now][1]);
                }
        vec.push_back(now);
        ch[now][0]=ch[now][1]=f[now]=cnt[now]=v[now]=0;
    }
    else del(id,ch[now][x>v[now]],x);
}
void erase(int x,int y){
    for (;x<=n;x+=x&-x) del(x,rt[x],y);
}
int get_sum(int id,int now,int x){
    if (!now) return 0;
    if (v[now]<=x){
        int y=get_sum(id,ch[now][1],x);
        if (!y) cur=now;
        return y+cnt[ch[now][0]]+1;
    }
    else return get_sum(id,ch[now][0],x);
}
int query(int x,int y){
    int sum=0;
    for (;x;x-=x&-x){
        cur=0;
        sum+=get_sum(x,rt[x],y);
        splay(x,cur);
    }
    return sum;
}
int get_ans(int x,int y,int z){
    int l=0,r=inf;
    for (;l<r;){
        int mid=(l+r)>>1;
        if (query(y,mid)-query(x-1,mid)>=z) r=mid;
            else l=mid+1;
    }
    return l;
}
int main(){
    int D;
    for (scanf("%d\n",&D);D--;){
        memset(ch,0,sizeof ch);
        memset(f,0,sizeof f);
        memset(cnt,0,sizeof cnt);
        memset(rt,0,sizeof rt);
        vec.clear();tot=0;
        scanf("%d%d\n",&n,&m);
        for (int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            insert(i,a[i]);
        }
        scanf("\n");
        for (int i=1;i<=m;i++){
            char c;int x,y,z;
            scanf("%c%d%d",&c,&x,&y);
            if (c=='Q'){
                scanf("%d",&z);
                int ans=get_ans(x,y,z);
                printf("%d\n",ans);
            }
            else{
                erase(x,a[x]);
                insert(x,y);
                a[x]=y;
            }
            scanf("\n");
        }
    }
}

整体二分,这是一种时间空间复杂度都十分优秀的做法,缺点是不支持在线操作。
把读入的 n 个数看成插入操作,修改操作看成删除一个数再插入一个数。二分的时候依然是二分答案,按顺序进行操作,对于那些sumk的询问,可以知道答案区间一定在 [l,mid] ;对于 sum<k ,操作的值 mid 的操作对它们的影响是固定的,只要把 k 减去sum即可。然后把答案或操作权值在 [l,mid] 的询问和操作分到左边,其它的分到右边,继续递归分治。区间求和可以用树状数组或者线段树。
时间复杂度 O(nlognlogs) O(nlog2n) ,空间复杂度 O(n)
常数也很小,实际运行时间简直快的飞起。

#include <algorithm>
#include <cstdio>
const int inf=1000000000;
int a[50005],f[50005],ans[10005],n,m,tot,cnt;
struct P{
    int id,x,y,z;
}q[70005],q1[70005],q2[70005];
void init(){
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        q[i]=(P){0,i,a[i],1};
    }
    scanf("\n");
    tot=n;cnt=0;
    for (int i=1;i<=m;i++){
        char ch;int x,y,z;
        scanf("%c%d%d",&ch,&x,&y);
        if (ch=='Q'){
            scanf("%d",&z);
            q[++tot]=(P){++cnt,x-1,y,z};
        }
        else{
            q[++tot]=(P){0,x,a[x],-1};
            q[++tot]=(P){0,x,y,1};
            a[x]=y;
        }
        scanf("\n");
    }
}
void add(int x,int y){
    for (;x<=n;x+=x&-x) f[x]+=y;
}
int query(int x){
    int sum=0;
    for (;x;x-=x&-x) sum+=f[x];
    return sum;
}
void work(int l,int r,int vl,int vr){
    if (l>r) return;
    if (vl==vr){
        for (int i=l;i<=r;i++)
            if (q[i].id) ans[q[i].id]=vl;
        return;
    }
    int mid=(vl+vr)>>1,t1=0,t2=0;
    for (int i=l;i<=r;i++)
    if (q[i].id){
        int sum=query(q[i].y)-query(q[i].x);
        if (q[i].z<=sum) q1[++t1]=q[i];
            else q[i].z-=sum,q2[++t2]=q[i];
    }
    else if (q[i].y<=mid) add(q[i].x,q[i].z),q1[++t1]=q[i];
        else q2[++t2]=q[i];
    for (int i=l;i<=r;i++)
        if (!q[i].id && q[i].y<=mid) add(q[i].x,-q[i].z);
    for (int i=1;i<=t1;i++) q[l+i-1]=q1[i];
    for (int i=1;i<=t2;i++) q[l+t1+i-1]=q2[i];
    work(l,l+t1-1,vl,mid);
    work(l+t1,r,mid+1,vr);
}
int main(){
    int D;
    for (scanf("%d\n",&D);D--;){
        init();
        work(1,tot,0,inf);
        for (int i=1;i<=cnt;i++) printf("%d\n",ans[i]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值