可持久化线段树/主席树

【例题】1. 静态区间第 k 小

luogu【模板】可持久化线段树2

给定长度为\(n\)的序列\(a_1,a_2,...,a_n\)\(m\)次询问,每次询问区间\([l,r]\)中第\(k\)大的元素是多少。

\(n,m\le 2×10^5,a_i\le 10^9\)

解法

首先,建立\(n\)颗权值线段树,分别代表区间\([1,i]\)中每一种权值出现的个数(离散化之后)。

这个过程显然要使用主席树,第\(i\)棵树是 在第\(i-1\)棵树 上将\(a[i]\)单点+1得到的。

int add(int rt, int l, int r, int x, int val) {//单点修改,返回新根编号
    rt=clone(rt);
    if(l == r){ t[rt].sum += val; return rt; }
    int mid = (l + r) >> 1;
    if(x <= mid) t[rt].l = add(t[rt].l, l, mid, x, val);
    else t[rt].r = add(t[rt].r, mid + 1, r, x, val);

    t[rt].sum = t[t[rt].l].sum + t[t[rt].r].sum;
    return rt;
}

主函数中:

for(int i = 1; i <= n; ++i) {
    root[i] = add(root[i - 1], 1, ntot, a[i], 1);
}

然后考虑如何求出区间\([l,r]\)的节点信息?

我们可以让两个指针rt1rt2分别从root[l-1]root[r]开始同步在线段树上跳。由于是同步的,所以rt1rt2所对应的(值域)区间是一样的。

我们每次计算出当前(值域)区间左儿子的值的个数为\(t[t[rt2].l].sum\ -\ t[t[rt1].l].sum\),其中\(t[rt].sum\)表示\(rt\)对应值域区间值的个数。

如果这个数 ≥ k,那么就递归到左儿子找。否则到右儿子找即可。

int query(int rt1, int rt2, int l, int r, int k) {
    if(l == r) return l;//l,r都是值域的端点
    int mid = (l + r) >> 1;
    int sum_l = t[t[rt2].l].sum - t[t[rt1].l].sum;

    if(sum_l >= k) return query(t[rt1].l, t[rt2].l, l, mid, k);
    else return query(t[rt1].r, t[rt2].r, mid + 1, r, k - sum_l);
}

完整代码:https://www.luogu.com.cn/paste/rbuywlkh


2.CF840D Destiny

题意:

给定长度为\(n\)的数列\(a_1,a_2,...,a_n\)\(m\)次询问。

每次给出三个参数\(l,r,k\)询问区间\([l,r]\)内是否存在出现次数严格大于\(\frac{r-l+1}{k}\)的数。如果存在就输出满足题意的最小值,否则输出\(-1\).

\(n,m\le 3\times 10^5\)

解法

建立\(n\)棵权值线段树,第\(i\)棵表示区间\([1,i]\)的本质不同的权值数量,显然用主席树完成。

对于询问,解决方法与例题类似,但也有不同

具体地,用两个指针rt1,rt2root[l-1]root[r]开始同步在线段树上跳,如果左半段的权值数量大于\(\frac{r-l+1}{k}\),答案就有可能在左边;右边同理。注意两边都要判断,并且要优先走左子树(因为题目求的是最小值。

但是这样的话就有可能两边都走,看看复杂度咋保证?

如果一次\(sum_{lson}>x\cdot\frac{r-l+1}{k}\),则\(sum_{rson}<(k-x)\cdot\frac{r-l+1}{k}\)。(因为这是权值线段树,所以\(sum_{lson},sum_{rson}\)加起来等于\(r-l+1\))。那么左边最多\(x\)个答案,最多搜索\(x\ log\ n\),同理右边最多\((k-x)\ log\ n\)次。

代码:https://codeforces.ml/contest/840/submission/130848969

posted @ 2021-10-05 11:06  hzy1  阅读(33)  评论(0编辑  收藏  举报