原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=4417
题意:求区间上有多少个数字比h小
思路:利用了主席树求第k小值的作用。如果区间第k小的值是x,那就说明在这个区间上至少有k个数字的值小于等于x。因此我们只要二分这个k值,在满足x<=h时,取那个最大的mid值就行了。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int T, n, m, q;
int a[maxn], t[maxn];
struct node {
int l, r, cnt;
} tree[maxn * 20];
int tot, rt[maxn];
void updata(int l, int r, int pre, int &cur, int x) {
cur = ++tot;
tree[cur] = tree[pre];
tree[cur].cnt++;
if(l == r) return ;
int mid = (l + r) >> 1;
if(mid >= x) updata(l, mid, tree[pre].l, tree[cur].l, x);
else updata(mid + 1, r, tree[pre].r, tree[cur].r, x);
}
int query(int l, int r, int pre, int cur, int k) {//求区间第k小
if(l == r) return l;
int sum = tree[tree[cur].l].cnt - tree[tree[pre].l].cnt;
int mid = (l + r) >> 1;
if(sum >= k) return query(l, mid, tree[pre].l, tree[cur].l, k);
else return query(mid + 1, r, tree[pre].r, tree[cur].r, k - sum);
}
int main() {
scanf("%d", &T);
int cas=1;
while(T--) {
tot = 0;
printf("Case %d:\n",cas++);
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
t[i] = a[i];
}
sort(t + 1, t + 1 + n);
m = unique(t + 1, t + 1 + n) - t - 1;
for(int i = 1; i <= n; i++) {
a[i] = lower_bound(t + 1, t + 1 + m, a[i]) - t;
}
rt[0] = 0;
for(int i = 1; i <= n; i++) {
updata(1, m, rt[i - 1], rt[i], a[i]);
}
for(int i = 1; i <= q; i++) {
int u, v, h;
scanf("%d%d%d", &u, &v, &h);
u++;
v++;
/*这边加1是因为我们主席树节点的编号是[1,n],
但是输入的数据是可能从0开始的
*/
int left = 1;
int right = v - u + 1;
int ans = 0;
while(left <= right) {
int mid = (left + right) >> 1;
int num = query(1, m, rt[u - 1], rt[v],mid);
if(t[num] <= h) {//记得比较的时候要将num离散化回来
/*
如果区间第mid大的数字是num,
那就说明在这个区间里面至少有mid个数字的值小于nuum
*/
if(ans<mid) ans=mid;
left=mid+1;
}
else right=mid-1;
}
printf("%d\n",ans);
}
}
return 0;
}