zoukankan      html  css  js  c++  java
  • (指针主席树简单介绍)第k小数

    题面简介:给一个长度为n((n<=2e5))数组序列((|ai|<=1e9)),有m((m<=2e5))次查询,每次查询区间([l,r])中第k小的数。

    还是指针香,虽然常数大了一点,但实现起来速度能快不少

    前置知识:知道数组主席树的写法,或者差不多知道主席树的原理。需要会一点指针。

    主席树,即可持久化线段树,可以记录线段树的多个历史版本,来达到快速查询历史信息的数据结构。

    在第k小数中,以给定数组中每个数字下标建立更新权值线段树,如果不使用主席树进行更新并记录历史版本的话,空间将会是n*size(tree)的,即n棵大小为size(tree)的线段树。

    主席树大幅减少了建立的节点数,每个版本通过新建一整条分支,分支上的节点和之前的版本共用没有更新的节点,解决了重复的节点建立。

    函数详解:

    struct tree {
        tree* ls, * rs;
        int cnt;
        tree() {
            ls = rs = NULL;
            cnt = 0;
        }
    };
    //开内存池
    tree* root[maxn];
    tree* pool = (tree*)malloc(sizeof(tree) * (21 * maxn));
    tree* cnt = pool;
    

    首先是主席树节点。指针实现的常数大,写法较数组写法简单一些,用malloc开内存池比动态开点优化了一些内存和时间。

    tree* build(int l, int r)
    {
        tree* node = ++cnt;
        if (l == r)return node;
        int mid = l + r >> 1;
        node->ls = build(l, mid);
        node->rs = build(mid + 1, r);
        return node;
    }
    

    然后是build函数,将区间分治并构造出线段树,当l==r时返回节点指针。由于节点中没有存储其所包含的区间,所以在之后的查找中需要推算得到l和r。

    tree* insert(tree*& p, int l, int r, int x)
    {
        tree* q = ++cnt;//新建一个点
        *q = *p;//暂时现将p的数据复制上去,即q的ls、rs也指向p的ls、rs,之后q的ls或rs将会被更新成后继新建的节点
        if (l == r)//权值线段树的更新操作
        {
            q->cnt++;
            return q;//返回节点指针
        }
        int mid = l + r >> 1;
        if (x <= mid)q->ls = insert(p->ls, l, mid, x);//x<=mid,说明需要往左区间更新,右节点代表的右区间不需要更新
        else q->rs = insert(p->rs, mid + 1, r, x);//反之亦然
        q->cnt = q->ls->cnt + q->rs->cnt;//传递更新结果
        return q;//返回节点指针
    }
    

    insert应该算是主席树的核心代码,其作用是插入更新结果,并记录历史版本

    int query(tree*& q, tree*& p, int l, int r, int k)//历史版本q和历史版本p,这里查询的是区间(l,r),所以历史版本为l-1和r,然后把2个版本作差
    {
        if (l == r)return r;//二分的最终答案
        int cnt = q->ls->cnt - p->ls->cnt;//这里是左儿子之间的cnt作差!!!!!!!!!!!!!!!!!!!!!
        int mid = l + r >> 1;
        if (k <= cnt)return query(q->ls, p->ls, l, mid, k);//左儿子里面的数个数大于k,那就向左缩小范围
        else return query(q->rs, p->rs, mid + 1, r, k - cnt);//反之左边的个数没有到k,由于是找第k小数,那么现在的子查询就是去右区间找第k-cnt小数
    }
    

    query注释比较详细,需要注意的是每次的cnt都是左儿子的cnt之差

    #include<bits/stdc++.h>
    #include<unordered_map>
    #define ll long long
    #define fastio ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
    using namespace std;
    double pi = acos(-1);
    
    const double eps = 1e-6;
    const int maxn = 2e5 + 10;
    const ll inf = LLONG_MAX;
    
    unordered_map<int, int>id;
    int n, m;
    
    struct tree {
        tree* ls, * rs;
        int cnt;
        tree() {
            ls = rs = NULL;
            cnt = 0;
        }
    };
    
    //开内存池
    tree* root[maxn];
    tree* pool = (tree*)malloc(sizeof(tree) * (21 * maxn));
    tree* cnt = pool;
    
    int a[maxn];
    
    tree* build(int l, int r)
    {
        tree* node = ++cnt;
        if (l == r)return node;
        int mid = l + r >> 1;
        node->ls = build(l, mid);
        node->rs = build(mid + 1, r);
        return node;
    }
    
    tree* insert(tree*& p, int l, int r, int x)
    {
        tree* q = ++cnt;
        *q = *p;
        if (l == r)
        {
            q->cnt++;
            return q;
        }
        int mid = l + r >> 1;
        if (x <= mid)q->ls = insert(p->ls, l, mid, x);
        else q->rs = insert(p->rs, mid + 1, r, x);
        q->cnt = q->ls->cnt + q->rs->cnt;
        return q;
    }
    
    int query(tree*& q, tree*& p, int l, int r, int k)
    {
        if (l == r)return r;
        int cnt = q->ls->cnt - p->ls->cnt;
        int mid = l + r >> 1;
        if (k <= cnt)return query(q->ls, p->ls, l, mid, k);
        else return query(q->rs, p->rs, mid + 1, r, k - cnt);
    }
    
    int main()
    {
        fastio;
        cin >> n >> m;
        vector<int>num;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            num.push_back(a[i]);
        }
        sort(num.begin(), num.end());
        num.erase(unique(num.begin(), num.end()), num.end());
    
        for (int i = 0; i < num.size(); i++)
            id[num[i]] = i;
        root[0] = build(0, num.size() - 1);
        for (int i = 1; i <= n; i++)
            root[i] = insert(root[i - 1], 0, num.size() - 1, id[a[i]]);
    
        while (m--)
        {
            int l, r, k;
            cin >> l >> r >> k;
            cout << num[query(root[r], root[l - 1], 0, num.size() - 1, k)] << endl;
        }
        return 0;
    
    }
    
  • 相关阅读:
    单词篇:(单词应用10~11)
    单词篇:(单词识记11)
    单词篇:(单词识记10)
    单词篇:(单词识记8~9)
    单词篇:(单词应用9)
    单词篇:(单词识记8)
    单词篇:(单词应用6~7)
    单词篇:(单词识记7)
    单词篇:(单词识记6)
    单词篇:(单词应用4~5)
  • 原文地址:https://www.cnblogs.com/ruanbaiQAQ/p/14119700.html
Copyright © 2011-2022 走看看