• {{item}}
  • {{item}}
  • {{item}}
  • {{item}}
  • 天祈
  • {{item.name}}
  • {{item.name}}
  • {{item.name}}
  • {{item.name}}

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


题面;

img

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;
}
posted @ 2021-05-08 20:56  TanJI_C  阅读(62)  评论(0)    收藏  举报