• 区间第k小数 (可持久化线段树、主席树)


     题意:多次询问,每次询问某区间的第k小数。

    可持久化线段树:

    掺杂了一点前缀和的思想,对于每一个1 ~ i 的区间都建一个树,每个节点存的都是一个线段树,值存的是当前区间中初始数组按大小排序后[l, r]之间的数的个数,这个l,r指的是每个节点的左右端点。如果想求[l, r]区间内的第k小数,只需要同时遍历[1,l - 1] 以及 [1, r] 两个版本的线段树,因为即使版本不同,线段树的结构是不变的,所以可以发现,如果某个节点区间在老版本里面已经出现了x个数,那么在新版本中的当前区间需要减去x这样才是我们所求的区间里面数的数量,通过查找第k个数的位置找到第k小的数是哪个。

    有点乱,直接看代码吧。

    1. using namespace std;
    2. typedef long long ll;
    3. typedef pair<int, int> pii;
    4. typedef pair<int, string> pis;
    5. const int mod = 1e9 + 7;
    6. const int N = 2e6+ 10;
    7. int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
    8. int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
    9. int n, m, idx;
    10. int o[N], root[N];
    11. struct node{
    12. int l, r;
    13. int cnt;
    14. }tr[N];
    15. vector<int> nums;
    16. int find(int x){//找到对应的离散值
    17. return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
    18. }
    19. int build(int l, int r){// 建树
    20. int p = ++ idx;// 给当前节点分配序号
    21. if(l == r) return p;// 区间不可再分,直接返回当前节点序号即可
    22. int mid = l + r >> 1; // 找到区间的中点
    23. tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);// 分别对左右儿子进行建树
    24. return p;// 将当前树的序号返回
    25. }
    26. int insert(int p, int l, int r, int x){// 插入某个元素,p为上一个版本的当前区间的树序号
    27. int q = ++ idx;// 为当前子树分配序号
    28. tr[q] = tr[p]; // 继承老版本中当前子树的信息
    29. if(l == r) {// 需要修改的就是当前位置,将cnt加一即可
    30. tr[q].cnt ++;
    31. return q;
    32. }
    33. int mid = l + r >> 1;
    34. if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);// 需要插入得位置在左儿子
    35. else tr[q].r = insert(tr[p].r, mid + 1, r, x);// 在右儿子
    36. tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt; // 更新当前版本的当前区间的cnt状态
    37. return q;//返回当前的序号
    38. }
    39. int query(int q, int p, int l, int r, int k){// 查询l,r区间的第k小数,q为当前版本,p为老版本
    40. if(l == r) return r; // 找到所查元素
    41. int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;// 通过新老版本的差可以得出当前区间的真实数量
    42. int mid = l + r >> 1;
    43. if(k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);// 再左儿子查左儿子,更新新老版本的左儿子树的序号
    44. else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);// 更新右儿子树的序号,以及新的k的状态
    45. }
    46. inline void sovle() {
    47. cin >> n >> m;
    48. for(int i = 1; i <= n; i ++){
    49. cin >> o[i];
    50. nums.push_back(o[i]);
    51. }
    52. sort(nums.begin(), nums.end());
    53. nums.erase(unique(nums.begin(), nums.end()), nums.end());//以上都是在进行离散化操作
    54. root[0] = build(0, nums.size() - 1);// 建立一个空的线段树,用于下一次操作继承,哨兵作用
    55. for(int i = 1; i <= n; i ++) // 没差一次就建立一个新版本的树
    56. root[i] = insert(root[i - 1], 0, nums.size() - 1, find(o[i]));
    57. while(m --){
    58. int l, r, k;
    59. cin >> l >> r >> k;
    60. int i = query(root[r], root[l - 1], 0, nums.size() - 1, k);//查询操作
    61. cout << nums[i] << endl;
    62. }
    63. }
    64. signed main(void) {
    65. IOS;
    66. int t = 1;
    67. // cin >> t;
    68. while(t --) sovle();
    69. return 0;
    70. }

  • 相关阅读:
    续-一个请求的过程
    命题和逻辑连接词
    Python3中用户注册小案例 - 解决中文字符问题 (代码)
    MyBatis映射文件深入
    Java使用原生API调用第三方接口
    安卓请求权限
    抽象类和接口
    (2023|ICLR,检索引导,交叉引导,EntityDrawBench)Re-Imagen:检索增强的文本到图像生成器
    大数据培训课程WordCount案例实操
    tcp的1对多模型C++处理逻辑
  • 原文地址:https://blog.csdn.net/qq_64468032/article/details/134550170