2024 Jiangsu Collegiate Programming Contest E. Divide 题解 主席树

06-17 492阅读

Divide

题目描述

Given an integer sequence a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1​,a2​,…,an​ of length n n n. For an interval a l , … , a r a_l,\ldots,a_r al​,…,ar​ in this sequence, a Reduce operation divides the maximum value of the interval by 2 2 2 (rounding down). If there are multiple maximum values, choose the one with the smallest index. There are q q q queries. Given three integers l , r , k l,r,k l,r,k each time, query the maximum value of the interval after performing k k k Reduce operations on the a l , … , a r a_l,\ldots,a_r al​,…,ar​ interval. The queries are independent of each other. That is to say, each time the query starts from the initially given sequence.

输入描述

The two integers n , q n,q n,q ( 1 ≤ n , q ≤ 1 0 5 1\le n,q\le 10^5 1≤n,q≤105) in the first line represent the sequence length and the number of queries.

2024 Jiangsu Collegiate Programming Contest E. Divide 题解 主席树

The second line contains n n n integers a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1​,a2​,…,an​ ( 0 ≤ a i ≤ 1 0 5 0\le a_i\le 10^5 0≤ai​≤105).

The next q q q lines each have three integers l , r , k l,r,k l,r,k ( 1 ≤ l ≤ r ≤ n , 0 ≤ k ≤ 1 0 9 1\le l\le r\le n,0\le k\le 10^9 1≤l≤r≤n,0≤k≤109), representing a query.

输出描述

For each query, output an integer in one line, representing the maximum value of the interval since the operation started from the initial sequence.

样例 #1

样例输入 #1

3 2
2 0 2
2 3 0
1 3 0

样例输出 #1

2
2

样例 #2

样例输入 #2

6 6
9 5 0 3 6 7
1 4 7
3 3 233
6 6 0
3 4 4
4 5 15
1 1 0

样例输出 #2

1
0
7
0
0
9

思路

将题目所给数组进行扩充。例如,对于样例#2,数组 9 5 0 3 6 7 可通过对每个数不断除以 2 2 2 直至为 0 0 0 ( 0 0 0也加入扩充后数组) 扩充为 9 4 2 1 0 5 2 1 0 0 3 1 0 6 3 1 0 7 3 1 0。这样,对于每个询问,相当于在扩充后的数组中寻找第 k + 1 k+1 k+1 大值。但由于询问中的区间是原数组中的区间,所以我们需利用 map 构建原数组区间到扩充后数组区间的映射。此外,对于询问中 k + 1 k+1 k+1 的值大于扩充后区间中元素个数的情况,需要特判答案为 0 0 0。

代码

#include 
using namespace std;
using i64 = long long;
typedef long long ll;
const int maxn = 2e6;
int tot, n, m;
int sum[(maxn 
    return lower_bound(ind + 1, ind + len + 1, val) - ind;
}
int build(int l, int r)
{
    int root = ++tot;
    if (l == r)
    {
        return root;
    }
    int mid = l + r  1;
    ls[root] = build(l, mid);
    rs[root] = build(mid + 1, r);
    return root;
}
int update(int k, int l, int r, int root)
{
    int dir = ++tot;
    ls[dir] = ls[root], rs[dir] = rs[root];
    sum[dir] = sum[root] + 1;
    if (l == r)
    {
        return dir;
    }
    int mid = l + r > 1;
    if (k 
        ls[dir] = update(k, l, mid, ls[dir]);
    }
    else
    {
        rs[dir] = update(k, mid + 1, r, rs[dir]);
    }
    return dir;
}
int query(int u, int v, int l, int r, int k)
{
    int mid = l + r > 1;
    int x = sum[ls[v]] - sum[ls[u]];
    if (l == r)
    {
        return l;
    }
    if (k 
        return query(ls[u], ls[v], l, mid, k);
    }
    else
    {
        return query(rs[u], rs[v], mid + 1, r, k - x);
    }
}
map
    cin > n >> m;
    int idx = 0; // 扩充数组的索引
    int x;
    for (int i = 1; i 
        cin > x;
        mp[i].first = idx + 1; // 一个原数组中的元素x经扩充后的区间的左端点
        while (x)              // 元素x扩充,扩充到区间[mp[i].first,mp[i].second]里面
        {
            a[++idx] = x;
            x /= 2;
        }
        a[++idx] = 0;       // ai可能等于0,所以要单独将0加入扩充后区间
        mp[i].second = idx; // 一个原数组中的元素x经扩充后的区间的右端点
    }
    // 离散化构建主席树,主席树可用来求出扩充后数组的区间第k小值
    memcpy(ind, a, sizeof(ind));
    sort(ind + 1, ind + idx + 1);
    len = unique(ind + 1, ind + idx + 1) - ind - 1;
    rt[0] = build(1, len);
    for (int i = 1; i 
        rt[i] = update(getid(a[i]), 1, len, rt[i - 1]);
    }
}
int l, r, k;
void work()
{
    while (m--)
    {
        cin > l >> r >> k;
        k++; // 当k=i时,求的是第i+1大数,所以k需要++
        // 主席树询问区间:左开右闭
        int left = mp[l].first - 1;
        int right = mp[r].second;
        // 因为左开右闭,所以区间长度即为right-left,而当区间长度小于k+1时,第k+1大值一定为0
        if (right - left 

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]