看到这题时确实没有想到一个好的方法解题,纠结了很久之后搜了题解,原来解问题固定的算法,划分树。
划分树是靠线段树作为辅助工具,原理和快排相似。
划分树建树的过程和快速排序很类似,构建起来也比较简单,关键是在查询操作不太好理解。
poj 2104 AC代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100000+10;
const int DEEP = 20;
struct node {
int l, r;
int mid() {
return (l+r)>>1;
}
}v[maxn<<2];
int data[maxn];//保存排序之后的数组
int seg[DEEP][maxn];//保存第d层划分之后的数组
int lessMid[DEEP][maxn];//保存第d层在i之前(包括i)小于data[mid]的数的数目
void build(int l, int r, int n, int d) {
v[n].l = l, v[n].r = r;
if (l==r) return ;
int mid = v[n].mid();
int lsame = mid-l+1;//确定与data[mid]相等并且被划分到左侧的数目
for (int i=l; i<=r; ++i)
if (seg[d][i]<data[mid])
--lsame;
int same = 0, tl = l, tr = mid+1;
for (int i=l; i<=r; ++i) {
if (i==l)
lessMid[d][i] = 0;
else
lessMid[d][i] = lessMid[d][i-1];
if (seg[d][i]>data[mid])
seg[d+1][tr++] = seg[d][i];
else if (seg[d][i]<data[mid]) {
++lessMid[d][i];
seg[d+1][tl++] = seg[d][i];
}
else if (seg[d][i]==data[mid]) {
if (lsame>same) {//说明相等时左侧没放满
++same;
++lessMid[d][i];
seg[d+1][tl++] = seg[d][i];
}
else//相反,左侧放满了,现在放在右侧
seg[d+1][tr++] = seg[d][i];
}
}
build(l, mid, n<<1, d+1);
build(mid+1, r, n<<1|1, d+1);
}
int query(int l, int r, int n, int k, int d) {
if (l==r)
return seg[d][l];
int s;//区间[l,r]中有多少小于data[mid]的数被放在左边
int ss;//区间[v[n].l,l-1]中有多少小于data[mid]的数被放在左边
if (l==v[n].l)
ss = 0;
else
ss = lessMid[d][l-1];
s = lessMid[d][r] - ss;
if (s>=k) {
int newl = v[n].l+ss;
int newr = v[n].l+ss+s-1;
return query(newl, newr, n<<1, k, d+1);
}
else {
int mid = v[n].mid();
int bb = l-v[n].l -ss;//区间[v[n].l,l-1]中有多少个数被放在右边
int b = r-l+1 -s;//区间[l,r]中有多少个数被放在右边
int newl = mid+bb+1;
int newr = mid+bb+b;
return query(newl, newr, n<<1|1, k-s, d+1);
}
}
int main()
{
int n, m;
while (~scanf("%d%d", &n, &m))
{
for (int i=1; i<=n; ++i)
{
scanf("%d", &data[i]);
seg[1][i] = data[i];
}
sort(data+1, data+n+1);
build(1, n, 1, 1);
while (m--)
{
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
printf("%d\n", query(l, r, 1, k, 1));
}
}
return 0;
}