Bzoj 1901 Dynamic Rankings

【题意】

Description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。 第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

Input

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Output

Sample Input

5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

Sample Output

3
6

HINT

20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。

【解题方法】动态第K大,主席树套树状数组。

首先静态主席树这个东西其实比较好懂,就是对于每个前缀[1,1],[1,2]....[1,n]都建一颗线段树,将数据离散化后,在线段树中统计区间内所有值出现的次数,每一个线段树都是相同的形态,那么这样我们得到一个很好的性质,这些线段树可以“相减”,假设当前查询的是区间[l,r]内第k大的值,那么我们用前缀[1, r]这个线段树和前缀[1, l-1]这颗线段树通过相减加上二分就可以找到答案。由于相邻两颗线段树最多只有logn个节点不同,我们没有必要完全新建一颗线段树,只需要把相同的结点用指针指一下就行,然后新建logn个结点,这样一来时空复杂度为n*logn。

对于动态查询的主席树,如果修改一个点的值,我们肯定不能修改所有包含这个点的线段树,时间复杂度无法承受。

那么我们就可以套上树状数组,个人感觉这里比较难懂。

树状数组的每个节点都是一颗线段树,但这棵线段树不再保存每个前缀的信息了,而是由树状数组的sum函数计算出这个前缀的信息,那么显而易见这棵线段树保存的是辅助数组S的值,即S=A[i-lowbit+1]+...+A[i],其中A[i]表示值为i的元素出现的次数。

那么对于每次修改,我们要修改树状数组上的logn棵树,对于每棵树,我们要修改logn个结点,所以时空复杂度为

O((n+q)*logn*logn),由于这道题n比较大,查询次数q比较小,所以我们可以初始时建立一颗静态的主席树,树状数组只保存每次修改的信息,那么时空复杂度降为了O(n*logn+q*logn*logn)。

【参考BLOG1】 http://blog.csdn.net/u014664226/article/details/47839973

【参考BLOG2】 http://www.cnblogs.com/kuangbin/p/3308118.html

【AC 代码】

//
//BZOJ 1901
//Created by just_sort 2016/8/22
//All Rights Reserved
//
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 60000+100;
const int maxm = maxn*40;
int n,q,m,tot,a[maxn];
int T[maxn];
vector<int>xs;
int use[maxn],S[maxn];
int getid(int x)
{
    return lower_bound(xs.begin(),xs.end(),x)-xs.begin();
}
struct Q
{
    int l,r,k;
    int ty;
    Q(int ll,int rr,int kk,int ii):l(ll),r(rr),k(kk),ty(ii) {}
    Q() {}
} query[10010];
int lson[maxm],rson[maxm],c[maxm];
int Build(int l,int r)
{
    int root=tot++;
    c[root]=0;
    if(l!=r)
    {
        int mid=(l+r)/2;
        lson[root]=Build(l,mid);
        rson[root]=Build(mid+1,r);
    }
    return root;
}
int Insert(int root,int pos,int val)
{
    int newroot=tot++,tmp=newroot;
    int l=0,r=m-1;
    c[newroot]=c[root]+val;
    while(l<r)
    {
        int mid=(l+r)/2;
        if(pos<=mid)
        {
            lson[newroot]=tot++,rson[newroot]=rson[root];
            newroot=lson[newroot],root=lson[root];
            r=mid;
        }
        else
        {
            lson[newroot]=lson[root],rson[newroot]=tot++;
            newroot=rson[newroot],root=rson[root];
            l=mid+1;
        }
        c[newroot]=c[root]+val;
    }
    return tmp;
}
int lowbit(int x)
{
    return x&-x;
}
void add(int x,int pos,int val)
{
    while(x<=n)
    {
        S[x]=Insert(S[x],pos,val);
        x+=lowbit(x);
    }
}
int getsum(int x)
{
    int ret=0;
    while(x)
    {
        ret+=c[lson[use[x]]];
        x-=lowbit(x);
    }
    return ret;
}

int querykth(int left,int right,int k)
{
    int left_root=T[left-1],right_root=T[right];
    int l=0,r=m-1;
    for(int i=right; i; i-=lowbit(i)) use[i]=S[i];
    for(int i=left-1; i; i-=lowbit(i)) use[i]=S[i];
    while(l<r)
    {
        int mid=(l+r)/2;
        int temp=getsum(right)-getsum(left-1)+c[lson[right_root]]-c[lson[left_root]];
        if(temp>=k){
            r=mid;
            for(int i=left-1; i; i-=lowbit(i)) use[i]=lson[use[i]];
            for(int i=right; i; i-=lowbit(i))  use[i]=lson[use[i]];
            left_root=lson[left_root];
            right_root=lson[right_root];
        }
        else{
            l=mid+1;
            k-=temp;
            for(int i=left-1; i; i-=lowbit(i)) use[i]=rson[use[i]];
            for(int i=right; i; i-=lowbit(i))  use[i]=rson[use[i]];
            left_root=rson[left_root];
            right_root=rson[right_root];
        }
    }
    return l;
}

int main()
{
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        tot=m=0;
        xs.clear();
        for(int i=1; i<=n; i++){
            scanf("%d",&a[i]);
            xs.push_back(a[i]);
        }
        char op[10];
        for(int i=0; i<q; i++){
            scanf("%s",op);
            if(op[0]=='Q'){
                query[i].ty=0;
                scanf("%d%d%d",&query[i].l,&query[i].r,&query[i].k);
            }else{
                query[i].ty=1;
                scanf("%d%d",&query[i].l,&query[i].r);
                xs.push_back(query[i].r);
            }
        }
        sort(xs.begin(),xs.end());
        xs.resize(unique(xs.begin(),xs.end())-xs.begin());
        m = xs.size();
        T[0]=Build(0,m-1);
        for(int i=1; i<=n; i++) T[i]=Insert(T[i-1],getid(a[i]),1);
        for(int i=1; i<=n; i++) S[i]=T[0];
        for(int i=0; i<q; i++){
            if(query[i].ty==0) printf("%d\n",xs[querykth(query[i].l,query[i].r,query[i].k)]);
            else{
                add(query[i].l,getid(a[query[i].l]),-1);
                add(query[i].l,getid(query[i].r),1);
                a[query[i].l]=query[i].r;
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值