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

(adviser)

Site: http://zhuzeyuan.hp.infoseek.co.jp/index.files/our_contest_20040619.htm


Author: XIN, Tao

Source: Online Contest of Christopher's Adventure

题意:

给你一个长度为n(1 <= N <= 50,000)的序列。序列中每一个值都不超过1e9.然后有m(1 <= M <= 10,000)次操作。

1.Q i j k  询问i,j间第k大的值。

2.C i t     把序列的第i个值改成t。

思路:

明显的主席树。

网上关于主席树的资料比較少。所以介绍下主席树。一方面让刚開始学习的人少走弯路。还有一方面方便自己温习。避免过段时间自己的代码都不认识了。。。

先讲下主席树相关的概念吧。

1.什么是主席树。

主席树貌似是网上流传的一种叫法。貌似学名叫函数式线段树。相关概念能够自行百度。

2.主席树有什么用。

主席树能够求解区间第k大问题。当然这个划分树也能够完毕且时间和空间都比主席树更优。那学主席树还有什么用。当然实用啦。划分树仅仅能解决静态第k大问题。也就是说。假设序列里的值有更新的话划分树就不再适用了。这个时候就体现主席树的优势了。主席树的其它应用还没研究。等遇到了再补充。

3.主席树究竟是什么。

事实上所谓的主席树就是一堆线段树。一堆线段树?一颗线段树就耗那么多内存了。一堆不会爆么?这个就是主席树的精华了。后面再解释。

4.主席树是怎么实现查询区间第k大问题的呢?

        先从最主要的问题入手吧。假设区间是固定的就为[1,n]。然后询问区间的第k大。非常easy吧。排个序即可了,可是我们要讲的是线段树的做法。考虑到序列的值可能非常大。我们能够先对这n个值hash一下映射到1-n的范围内。然后我们就把这n个值插入线段树。线段树的每一个结点维护子树中已经插入值的个数。那么查找第k大就非常easy了。假设k>左子树值的个数。那么就在右子树中找第(k-左子树值个数)大值。否则在右子树中找。递归进行直到到叶子结点。然后还原hash值即可了。

        如今关键就是怎么解决区间不是[1,n]的问题了。假如我们要查询区间[l,r]的第k大值。假设我们有一颗线段树R插入了里面的值。当然就跟区间[1,n]的方法一样了。假如我们建了n棵上面的线段树。第i棵插入了[1,i]的值。感觉前缀的思想真的非常巧妙。跟字符串的前缀有异曲同工之妙。第i棵线段树的根为T[i]。那么怎么求[l,r]的第k大呢。事实上和上面方法差点儿相同。我们关键就是要知道线段树R区间[l,r]内的值在左子树的值有多少个。在右子树有多少个。这个时候前缀的优势就来了。(T[r]左子树值的个数-T[l-1]左子树值的个数)不就是R在左子树值的个数么。然后递归定位到叶子结点即可了。

        但问题又来了。那么多棵线段树。就算内存不爆。建树的时间也该爆了吧。精华部分来了。不得不叹服前人的智慧啊。因为这n棵线段树结构形态一致。结点个数都一样。这个非常好理解吧。都是维护[1,n]值出现个数嘛。而更加爽的是。T[i+1]比T[i]多插一个值a[i+1].试想假设a[i+1]被插入了T[i+1]的左子树。那么T[i+1]的右子树和T[i]的左子树将全然同样。进入右子树同理。那所以T[i+1]的右子树全然不用建了。直接把T[i+1]右子树指针指向T[i]右子树即可了。共享结点。这是多么机智。一下攻克了一半的结点。这种做法递归进行。也就是T[i+1]仅仅需建log2(n)的结点。这样就能够解决静态第k大了。

能够拿这个题开刀嘿嘿~

        如今剩下的就是解决动态第k大问题了。这个地方我理解了非常久。才明确。明确了事实上非常easy。假设我们更新了arr[i]那么它将会影响T[i]~T[n]。难道我们要一个一个改么。这样改显然时间上不同意。可是你会发现它的改变对T[i]~T[n]的影响是一样的。假设我们把影响(做子树添加多少值。右子树添加多少结点)记录下来。那我们就仅仅需用原始值减去变化值就好了。变化值我们能够用树状数组来记录。我们把变化当作一个值,那么就成了单点更新区间求和问题了。仅仅是这里不是一个值而是一个线段树而已,可是我们能够类似的处理。

也就是每一个树状数组的结点都是一棵线段树。哈哈。真是刺激。那么这个问题就圆满攻克了。分析下时空复杂度。首先建一棵空树m*log2(m)。m为hash后值的个数。然后建n个树.n*log2(m)。然后q次查询操作.2*q*log2(m).所以总时间复杂度为。O((m+n+2*q)*log2(m))。空间复杂度。4*m+n*lon2(m)。

以下附上代码:

#include<algorithm>
#include<iostream>
#include<string.h>
#include<sstream>
#include<stdio.h>
#include<math.h>
#include<vector>
#include<string>
#include<queue>
#include<set>
#include<map>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=60010;
const int maxm=2500010;
int ls[maxm],rs[maxm],c[maxm];//ls,rs左右儿子指针。c存值的个数
int arr[maxn],H[maxn],T[maxn];//arr存原序列.H存排序后值。T[i]第i棵线段树的根
int s[maxn],ua[maxn],ub[maxn],*use;//s为树状数组结点。当然也是线段树的根啦。
int n,m,tot;
struct node
{
int l,r,k;
} qs[10010];//因为要先hash。
void init()//hash初始化
{
sort(H,H+m);
m=unique(H,H+m)-H;
}
int Hash(int x)
{
return lower_bound(H,H+m,x)-H;
}
int build(int L,int R)//建空树
{
int rt=tot++,mid;
c[rt]=0;
if(L!=R)
{
mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
}
return rt;
}
int Insert(int prt,int x,int val)
{
int nrt=tot++,tp=nrt,l=0,r=m-1,mid;
c[nrt]=c[prt]+val;
while(l<r)//非递归插入。节省内存。
{
mid=(l+r)>>1;
if(x<=mid)
{
ls[nrt]=tot++,rs[nrt]=rs[prt];//共享结点
prt=ls[prt],nrt=ls[nrt];
r=mid;
}
else
{
ls[nrt]=ls[prt],rs[nrt]=tot++;
prt=rs[prt],nrt=rs[nrt];
l=mid+1;
}
c[nrt]=c[prt]+val;
}
return tp;
}
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int p,int d)//树状数组更新
{
while(x<=n)
{
s[x]=Insert(s[x],p,d);
x+=lowbit(x);
}
}
int sum(int x)
{
int ret=0;
while(x)
{
ret+=c[ls[use[x]]];
x-=lowbit(x);
}
return ret;
}
int qu(int L,int R,int k)
{
int lrt=T[L-1],rrt=T[R],l=0,r=m-1,mid,tp,i,sa,sb;
for(i=L-1,use=ua;i;i-=lowbit(i)) use[i]=s[i];
sb=sum(L-1);
for(i=R ,use=ub;i;i-=lowbit(i)) use[i]=s[i];
sa=sum(R);
while(l<r)
{
mid=(l+r)>>1;
tp=sa-sb+c[ls[rrt]]-c[ls[lrt]];//初始值加改变值
if(k<=tp)
{
r=mid;
lrt=ls[lrt],rrt=ls[rrt];
for(i=L-1,use=ua;i;i-=lowbit(i)) use[i]=ls[use[i]];//计算相应子树改变
sb=sum(L-1);
for(i=R ,use=ub;i;i-=lowbit(i)) use[i]=ls[use[i]];
sa=sum(R);
}
else
{
l=mid+1;
k-=tp;
lrt=rs[lrt],rrt=rs[rrt];
for(i=L-1,use=ua;i;i-=lowbit(i)) use[i]=rs[use[i]];
sb=sum(L-1);
for(i=R ,use=ub;i;i-=lowbit(i)) use[i]=rs[use[i]];
sa=sum(R);
}
}
return l;
}
int main()
{
int i,q,cas;
char op[10];
scanf("%d",&cas);
while(cas--)
{
scanf("%d%d",&n,&q);
tot=m=0;
for(i=1;i<=n;i++)
scanf("%d",&arr[i]),H[m++]=arr[i];
for(i=0;i<q;i++)
{
scanf("%s",op);
if(op[0]=='Q')
scanf("%d%d%d",&qs[i].l,&qs[i].r,&qs[i].k);
else
{
scanf("%d%d",&qs[i].l,&qs[i].r);
qs[i].k=-INF,H[m++]=qs[i].r;
}
}
init();
T[0]=build(0,m-1);
for(i=1;i<=n;i++)
T[i]=Insert(T[i-1],Hash(arr[i]),1);
for(i=1;i<=n;i++)
s[i]=T[0];
for(i=0;i<q;i++)
{
if(qs[i].k==-INF)
{
update(qs[i].l,Hash(arr[qs[i].l]),-1);
update(qs[i].l,Hash(qs[i].r),1);
arr[qs[i].l]=qs[i].r;//開始忘了改这里无限wa啊。。。
}
else
printf("%d\n",H[qu(qs[i].l,qs[i].r,qs[i].k)]);
}
}
return 0;
}

zoj 2112 Dynamic Rankings(主席树&amp;动态第k大)的更多相关文章

  1. ZOJ -2112 Dynamic Rankings 主席树 待修改的区间第K大

    Dynamic Rankings 带修改的区间第K大其实就是先和静态区间第K大的操作一样.先建立一颗主席树, 然后再在树状数组的每一个节点开线段树(其实也是主席树,共用节点), 每次修改的时候都按照树 ...

  2. ZOJ 2112 Dynamic Rankings(树状数组+主席树)

    题意 \(n\) 个数,\(m\) 个操作,每次操作修改某个数,或者询问某个区间的第 \(K\) 小值. \(1 \leq n \leq 50000\) \(1 \leq m \leq 10000\) ...

  3. ZOJ 2112 Dynamic Rankings(树状数组套主席树 可修改区间第k小)题解

    题意:求区间第k小,节点可修改 思路:如果直接用静态第k小去做,显然我更改一个节点后,后面的树都要改,这个复杂度太高.那么我们想到树状数组思路,树状数组是求前缀和,那么我们可以用树状数组套主席树,求出 ...

  4. 主席树[可持久化线段树](hdu 2665 Kth number、SP 10628 Count on a tree、ZOJ 2112 Dynamic Rankings、codeforces 813E Army Creation、codeforces960F:Pathwalks )

    在今天三黑(恶意评分刷上去的那种)两紫的智推中,突然出现了P3834 [模板]可持久化线段树 1(主席树)就突然有了不详的预感2333 果然...然后我gg了!被大佬虐了! hdu 2665 Kth ...

  5. ZOJ 2112 Dynamic Rankings(动态区间第 k 大+块状链表)

    题目大意 给定一个数列,编号从 1 到 n,现在有 m 个操作,操作分两类: 1. 修改数列中某个位置的数的值为 val 2. 询问 [L, R] 这个区间中第 k 大的是多少 n<=50,00 ...

  6. 整体二分&cdq分治 ZOJ 2112 Dynamic Rankings

    题目:单点更新查询区间第k大 按照主席树的思想,要主席树套树状数组.即按照每个节点建立主席树,然后利用树状数组的方法来更新维护前缀和.然而,这样的做法在实际中并不能AC,原因即卡空间. 因此我们采用一 ...

  7. 整体二分(SP3946 K-th Number ZOJ 2112 Dynamic Rankings)

    SP3946 K-th Number (/2和>>1不一样!!) #include <algorithm> #include <bitset> #include & ...

  8. zoj2112 树状数组+主席树 区间动第k大

    Dynamic Rankings Time Limit: 10000MS   Memory Limit: 32768KB   64bit IO Format: %lld & %llu Subm ...

  9. poj 2104 主席树(区间第k大)

    K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 44940   Accepted: 14946 Ca ...

随机推荐

  1. log4j 1.2配置(转载)

    转载自:http://www.blogjava.net/kit-soft/archive/2009/08/28/292977.html 第一步:加入log4j-1.2.8.jar到lib下. 第二步: ...

  2. Nginx fastcgi_param解释

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;#脚本文件请求的路径 fastcgi_param QUERY_STRI ...

  3. 串行CPU设计

    一.概述 串行CPU工作流程 串行CPU的时序流程如下图所示:取指.译码.执行.回写. 其中,取指.回写是与存储器打交道:而译码与执行则是CPU内部自个儿的操作. 我们究竟想要CPU干什么?     ...

  4. SQL SERVER 2008 nvarchar 转换 deciaml 失败(nvarchar to decimal)

    转换数据发生 消息 8115,级别 16,状态 6,第 1 行 将 nvarchar 转换为数据类型 numeric 时出现算术溢出错误. nvarchar 是带很长小数,直接转换成decimal 失 ...

  5. 【译】UI设计基础(UI Design Basics)--导航(Navigation)(六)

    [译]UI设计基础(UI Design Basics)--导航(Navigation)(六)

  6. How to solve "The specified service has been marked for deletion" error

    There may be several causes which lead to the service being stuck in “marked for deletion”. Microsof ...

  7. Struts2 Tomcat的配置

    1. 下载Struts2包,网站http://struts.apache.org/download.cgi#struts2315 2. 将struts-2.3.15-all.zip 包解压到本地 3. ...

  8. tail tailf 使用

    tail -f tailf 用来查看日志的新增内容, tailf 能一直打印日志

  9. Best Pratices——Make the Web Faster

    https://developers.google.com/speed/ PageSpeed Rules Optimizing caching — keeping your application's ...

  10. qml学习:对象和属性

    qml学习:对象和属性 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 参考文档<<Qt及Qt Quick开发实战精解.pdf>> ...