【POJ 2104/HDU 2665】K-th Number【整体二分/主席树】
题意
给出一个数组,多次查询区间 [l,r] 内的第 k 小数。
思路
1. 主席树
一贯地思考的话,主席树是显然可做此题的:用第
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
不经离散化时是总值域大小,否则是