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


    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 1n,q105) in the first line represent the sequence length and the number of queries.

    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 0ai105).

    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 1lrn,0k109), 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 << 5) + 10], rt[maxn + 10], ls[(maxn << 5) + 10], rs[(maxn << 5) + 10];
    int a[maxn + 10], ind[maxn + 10], len;
    
    int getid(const int &val)
    {
        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 <= mid)
        {
            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 <= x)
        {
            return query(ls[u], ls[v], l, mid, k);
        }
        else
        {
            return query(rs[u], rs[v], mid + 1, r, k - x);
        }
    }
    
    map<int, pair<int, int>> mp; // 构建原来数组中下标到扩充后数组中下标的映射
    
    void init()
    {
        cin >> n >> m;
        int idx = 0; // 扩充数组的索引
        int x;
        for (int i = 1; i <= n; 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 <= idx; 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 < k)
                cout << 0 << '\n';
            else
                cout << ind[query(rt[left], rt[right], 1, len, right - left + 1 - k)] << '\n'; // right - left + 1 - k的运算是将求第k大值转化为求第right - left + 1 - k小值
        }
    }
    
    int main()
    {
        ios::sync_with_stdio(0);
        cin.tie(0);
    
        init();
        work();
    
        return 0;
    }
    
  • 相关阅读:
    Mysql三种日志(binlog,redolog,undolog)的作用和区别
    论文解读:《DeepKla:一种基于注意力机制的深度神经网络,用于蛋白质赖氨酸乳酸化位点预测》
    Linux - 零拷贝技术
    COCI 2021-2022 #1 - Volontiranje 题解
    docker命令学习
    C#基础:WPF中常见控件的布局基础
    AcWing基础部分Class2:高精度加减乘除、前缀和与差分
    Python Jupyter Notebook效率开发工具
    Unity类银河恶魔城学习记录7-8 P74 Pierce sword源代码
    Python趣味算法入门 - 牛顿迭代法求方程根
  • 原文地址:https://blog.csdn.net/bbc_plus/article/details/139550257