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

4 篇文章 0 订阅
1 篇文章 0 订阅

【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) 。每个元素在每一层被插入到树状数组中的次数为 0 1,所以这部分的时间复杂度是 O(nlog2nlog2a) 。总的来说,这是个 O(nlog22n) 的优秀离线算法,往往能避免写诸如主席树或树套树之类的复杂数据结构。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值