##[$P1801$ 黑匣子](https://www.luogu.com.cn/problem/P1801) 虽说是堆题,但也可以用主席树不是? 对于每个要$get$的地方,相当于询问区间为$[1,x]$,其实就是模板题啦 ```cpp {.line-numbers} #include #include #include using namespace std; const int N = 200010; //快读 int read() { int x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); } return x * f; } int n, m; int a[N], b[N], bl; // b和bl是一组,用于离散化的数组,bl为b的数组中有用数字的个数,一般下标0不放东西 struct Node { int l, r, cnt; } tr[N << 5]; int root[N], idx; //用于离散化的二分查找 int find(int x) { return lower_bound(b + 1, b + 1 + bl, x) - b; } //经典的主席树插入 void insert(int &u, int l, int r, int x) { tr[++idx] = tr[u]; //新开一个节点idx++,将新节点指向旧的tr[u] tr[idx].cnt++; //新节点的cnt,因为多插入了一个数字,所以个数+1,这样处理的话,省去了pushup u = idx; //因为是地址引用,需要回写u等于idx if (l == r) return; //如果已经到了叶子节点,上面的操作就足够了,可以直接返回,否则需要继续向下递归 int mid = (l + r) >> 1; if (x <= mid) insert(tr[u].l, l, mid, x); //因为tr[u]进入本函数时,最先把旧的复制过来,所以tr[u].l也是上一个版本的左儿子节点 else insert(tr[u].r, mid + 1, r, x); } // p:前面的版本,q:后面的版本,[l,r]:控制的范围 // k:要查找第k小的数字 int query(int p, int q, int l, int r, int k) { if (l == r) return l; int mid = (l + r) >> 1; int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt; if (k <= cnt) return query(tr[p].l, tr[q].l, l, mid, k); else return query(tr[p].r, tr[q].r, mid + 1, r, k - cnt); } int main() { n = read(), m = read(); for (int i = 1; i <= n; i++) a[i] = b[i] = read(); sort(b + 1, b + 1 + n); bl = unique(b + 1, b + 1 + n) - b - 1; for (int i = 1; i <= n; i++) { root[i] = root[i - 1]; //开新版本号i,抄袭上一个版本i-1的根节点 insert(root[i], 1, bl, find(a[i])); //向版本i中增加find(a[i])的值 } int k = 0; for (int i = 1; i <= m; i++) { int x = read(); printf("%d\n", b[query(root[0], root[x], 1, bl, ++k)]); } return 0; } ```