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 <bits/stdc++.h>
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;
}

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-09 03:08:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-09 03:08:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-06-09 03:08:03       82 阅读
  4. Python语言-面向对象

    2024-06-09 03:08:03       91 阅读

热门阅读

  1. 电商API在促进销售与营销中的影响

    2024-06-09 03:08:03       31 阅读
  2. Zookeeper 详解:分布式协调服务的核心概念与实践

    2024-06-09 03:08:03       34 阅读
  3. Pytorch中的广播机制

    2024-06-09 03:08:03       30 阅读
  4. Access数据中的SQL偏移注入

    2024-06-09 03:08:03       32 阅读
  5. 在Spark SQL中,fillna函数

    2024-06-09 03:08:03       29 阅读
  6. SELinux:安全增强型Linux

    2024-06-09 03:08:03       22 阅读
  7. 嵌入式C中Hex与Bin文件对比分析

    2024-06-09 03:08:03       32 阅读
  8. 数据结构学习笔记-二叉树

    2024-06-09 03:08:03       21 阅读
  9. Python使用Flask构建简单的web应用

    2024-06-09 03:08:03       36 阅读
  10. 深度学习 - softmax交叉熵损失计算

    2024-06-09 03:08:03       27 阅读