【POJ 2104/HDU 2665】K-th Number【整体二分/主席树】

来源:互联网 发布:胡公子的淘宝店 编辑:程序博客网 时间:2024/06/05 21:16

【POJ 2104/HDU 2665】K-th Number【整体二分/主席树】

题意

给出一个数组,多次查询区间[l,r]内的第k小数。

思路

1.主席树

一贯地思考的话,主席树是显然可做此题的:用第i个版本维护该数组第i个前缀的值域线段树,每次查询只需在两个版本的差分线段树上二分即可。

2.整体二分

不妨换位思考:主席树的过程实际上是先把区间信息分离出来,再进行二分答案。若是把这个先后顺序调转一下,即是整体二分的思想了。
何言整体?对于单个询问,为了判断二分的选择肢,我们得把整个区间遍历一遍,这就造成了最坏情况下O(n)的时间复杂度。但若是我们能同时判断多个询问的选择肢,以将O(n)的时间均摊开来,就成为了一种可行的算法。这即是整体二分的思想。

Codeisalwaysthefirstpriority:

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define re_ return#define inc(l, i, r) for(i=l; i<r; ++i)const int mxn=1<<17;int n, m, b[mxn], ans[mxn];struct que{int l, r, k, i;} a[mxn], q[mxn], ql[mxn], qr[mxn];//单个查询inline bool operator <(const que& a, const que& b){re_ a.k<b.k;}//---树状数组inline char add(int i, int x){for(;i<=n; i+=i&-i) b[i]+=x;}inline int sum(int i){    static int r;    for(r=0; i; i-=i&-i) r+=b[i];    re_ r;}//---char div(int s, int t, int l, int r)//在查询队列中的首尾地址和二分值域左右端点{    int i, x, y, z, mid=l+r>>1, tl=0, tr=0;    if(s==t || l==r-1)    {        inc(s, i, t) ans[q[i].i]=l;        re_ 0;    }    x=lower_bound(a, a+n, (que){0, 0, l, 0})-a;    y=lower_bound(a, a+n, (que){0, 0, mid, 0})-a;    inc(x, i, y) add(a[i].i, 1);//仅遍历在左值域内的元素,插入到对应位置的树状数组中    inc(s, i, t)//将答案在当前二分值域内的查询分为答案在左右子值域内的查询        if((z=sum(q[i].r)-sum(q[i].l-1))<q[i].k)//判断同时在左值域和该查询区间内的元素的个数是否小于k            q[i].k-=z, qr[tr++]=q[i];//注意,当该查询答案在右值域时,要把左值域内的元素对“第k小”的贡献考虑到。表现为“k-=z”。        else            ql[tl++]=q[i];    inc(x, i, y) add(a[i].i, -1);//树状数组是全局的,需要清空    inc(0, i, tl) q[s+i]=ql[i];    inc(0, i, tr) q[s+tl+i]=qr[i];    div(s, s+tl, l, mid), div(s+tl, t, mid, r);//递归处理左右子区间}int main(){    int i;    scanf("%d%d", &n, &m);    inc(0, i, n) scanf("%d", &a[i].k), a[i].i=i+1;    sort(a, a+n);//以值为关键字排序    inc(0, i, m)        scanf("%d%d%d", &q[i].l, &q[i].r, &q[i].k), q[i].i=i;    div(0, m, -(1<<30), 1<<30);    inc(0, i, m) printf("%d\n", ans[i]);    re_ 0;}

复杂度分析

首先,递归层数上界是log2a(a不经离散化时是总值域大小,否则是n)。由于每个询问都会在每一层恰被判断一次,所以这部分的时间复杂度是O(mlog2a)。每个元素在每一层被插入到树状数组中的次数为01,所以这部分的时间复杂度是O(nlog2nlog2a)。总的来说,这是个O(nlog22n)的优秀离线算法,往往能避免写诸如主席树或树套树之类的复杂数据结构。