ZOJ Problem Set

题目链接:Dynamic Rankings

 

Dynamic Rankings

Time Limit: 10 Seconds      Memory Limit: 32768 KB

The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.

Your task is to write a program for this computer, which

- Reads N numbers from the input (1 <= N <= 50,000)

- Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.


Input

The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.

The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format

Q i j k or
C i t

It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.

There're NO breakline between two continuous test cases.


Output

For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])

There're NO breakline between two continuous test cases.


Sample Input

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


Sample Output

3
6
3
6

题意:给一个数组,两种操作,第一个是询问[L,R]的第K大;第二种修改某个数;

题解:如果没有修改,单纯主席树模板就可以搞定,但如果修改的话,我们每次都得更新后面的树,复杂度简直爆炸O(m*n*logn);显然不可行。方法就是树状数组

套个主席树(很巧妙两个都可以快速求前缀和),。怎么套呢?我们可以先把给的数组按第K大静态的建好,接下来我们需要的就是维护修改量了,原来我们的树状数组每次修改的log(n)数,这里树状数组套主席树每次修改的是log(n)棵树,每次修改对那log(N)棵树与静态主席树更新一样,每次修改会新增log(n)个节点。这样在询问的时候只要同时询问log(n)树就可以得到修改量然后加上原来的数组的贡献二分询问就可以到第K大。(可能光看文字有点难懂^-^可以对着对着代码理解一下)

时间复杂度,每次修改logN棵树每棵树修改logn个节点,修改复杂度O(logn*logn),每次询问要二分要每次判断要同时访问log(n)棵树询问复杂度O(logn*logn),

总共m次操作,总体复杂度O(m*logn*logn);

#include<bits/stdc++.h>
#include<set>
#include<cstdio>
#include<iomanip>
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#define pb push_back
#define ll long long
#define PI 3.14159265
//#define ls l,m,rt<<1
//#define rs m+1,r,rt<<1|1
#define eps 1e-7
typedef unsigned long long ull;
const int mod=1e9+9;
const ll inf=0x3f3f3f3f3f3f3f;
const int maxn=5e4+5;
const int N=1e4+7;
using namespace std;
int a[maxn],b[maxn*2],sum[maxn*40],rt[maxn],st[maxn],ls[maxn*40],rs[maxn*40],qa[N],qb[N],qc[N];
char op[N][3];
int totx[100],toty[100];
int n,m,tot,tol,sz,t,tox,toy;
int us[maxn];
int lowbit(int x){return x&(-x);}
void built(int &o,int l,int r)
{
    o=++tot;
    sum[o]=0;
    if(l==r)return;
    int m=(l+r)>>1;
    built(ls[o],l,m);
    built(rs[o],m+1,r);
}
void update(int &o,int l,int r,int pre,int x,int val)
{
    o=++tot;
    sum[o]=sum[pre]+val;ls[o]=ls[pre];rs[o]=rs[pre];
    int m=(l+r)>>1;
    if(l==r)return;
    if(x<=m)update(ls[o],l,m,ls[pre],x,val);
    else update(rs[o],m+1,r,rs[pre],x,val);
}
void add(int x,int p,int val)//树状数组每次修改log(n)棵树
{
    for(int i=x;i<=n;i+=lowbit(i))update(st[i],1,sz,st[i],p,val);
}
int get_sum(int x)//得到[1~x]棵树的贡献和
{
    int ans=0;
    while(x)
    {
        ans+=sum[ls[us[x]]];
        x-=lowbit(x);
    }
    return ans;
}
int query(int left,int right,int k)//询问第k大
{
    int l=1,r=sz;
    int LL=rt[left-1],rr=rt[right];
    for(int i=left-1;i>0;i-=lowbit(i))us[i]=st[i];//要得到[L,R]的修改值需要得到get_sum(left-1),所以us[]用来保存[1,left-1]那log(n)棵树的根节点,下面同理
    for(int i=right;i>0;i-=lowbit(i))us[i]=st[i];//
    while(l<r)//二分询问第K大
    {
        int m=(l+r)>>1;
        int cnt=get_sum(right)-get_sum(left-1)+sum[ls[rr]]-sum[ls[LL]];//修改量加上原来数组的贡献
        if(cnt>=k)//如果左边的出现的数量大于k往左边走
        {
            r=m;
            for(int i=left-1;i>0;i-=lowbit(i))us[i]=ls[us[i]];
            for(int i=right;i>0;i-=lowbit(i))us[i]=ls[us[i]];
            LL=ls[LL];rr=ls[rr];
        }
        else
        {
            l=m+1;k-=cnt;
            for(int i=left-1;i>0;i-=lowbit(i))us[i]=rs[us[i]];
            for(int i=right;i>0;i-=lowbit(i))us[i]=rs[us[i]];
            LL=rs[LL],rr=rs[rr];
        }
    }
    return l;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        tol=0;tot=0;
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);b[++tol]=a[i];
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%s %d %d",op[i],&qa[i],&qb[i]);
            if(op[i][0]=='Q')scanf("%d",&qc[i]);
            else b[++tol]=qb[i];
        }
        sort(b+1,b+tol+1);
        sz=unique(b+1,b+1+tol)-b-1;//离散
        for(int i=1;i<=n;i++)
        {
            a[i]=lower_bound(b+1,b+1+sz,a[i])-b;
        }
        for(int i=1;i<=m;i++)
        {
            if(op[i][0]!='Q')qb[i]=lower_bound(b+1,b+1+sz,qb[i])-b;
        }
        built(rt[0],1,sz);
        for(int i=1;i<=n;i++)update(rt[i],1,sz,rt[i-1],a[i],1);//静态建好原来数组的主席树
        for(int i=1;i<=n;i++)st[i]=rt[0];//st数组是用来维护修改量的每棵树的根节点
        for(int i=1;i<=m;i++)
        {
            if(op[i][0]=='Q')
            {
                printf("%d
",b[query(qa[i],qb[i],qc[i])]);
            }
            else
            {
                add(qa[i],a[qa[i]],-1);//减去原来值的贡献
                a[qa[i]]=qb[i];
                add(qa[i],qb[i],1);//加上修改值的贡献
            }
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/lhclqslove/p/8748025.html