做题集——(字典树+启发式合并+dfs)Query on A Tree
题面;

Sample Input:
5 3
1 5 3 2 1
1 3
2 4
1 5
5 1
1 2 3 4 5
1 5
Output:
2
2
2
3
题意:
给定一个长度为n的序列,m次询问[l, r]区间内的最大的h值为多少,取h值的条件:序列中大于h的数字至少有h个即可取改值为h值。
思路:
取一个序列中第k大的数字,则算上该数则前面不小于该数的数字至少有k个(有相等的情况)。要求的是最小的k使k指向的数字小于k。几乎就是模板题了。
本题明显是要使用二分的,但是将二分融合到Ask函数中可以忽略此步。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
struct Node {
int l, r, sum;
}t[MAXN * 40];
int cnt = 0, root[MAXN];
int a[MAXN];
void Modify(int l, int r, int y, int& x, int pos) {
t[++cnt] = t[y];
t[cnt].sum++;
x = cnt;
if (l == r) return ;
int mid = l + r >> 1;
if (pos <= mid) Modify(l, mid, t[y].l, t[x].l, pos);
else Modify(mid + 1, r, t[y].r, t[x].r, pos);
}
int Ask(int l, int r, int L, int R, int k) {
if (l == r) return l;
int mid = l + r >> 1;
int tmp = t[t[R].r].sum - t[t[L].r].sum;//求的是前缀和, 如果前缀和的数量大于中间的数字
if (tmp + k > mid) return Ask(mid + 1, r, t[L].r, t[R].r, k);
else return Ask(l, mid, t[L].l, t[R].l, k + tmp);
}
int main() {
int n, m;
while (~scanf("%d%d", &n, &m)) {
cnt = 0;
memset(root, 0, sizeof(root));
memset(t, 0, sizeof(t));
memset(a, 0, sizeof(a));
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), Modify(1, n, root[i - 1], root[i], a[i]);
int x, y;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &x, &y);
printf("%d\n", Ask(1, n, root[x - 1], root[y], 0));
}
}
return 0;
}

浙公网安备 33010602011771号