zoukankan      html  css  js  c++  java
  • Kth number 主席树(可持久化线段树)

    Kth number

     HDU - 2665 
    Give you a sequence and ask you the kth big number of a inteval.

    InputThe first line is the number of the test cases. 
    For each test case, the first line contain two integer n and m (n, m <= 100000), indicates the number of integers in the sequence and the number of the quaere. 
    The second line contains n integers, describe the sequence. 
    Each of following m lines contains three integers s, t, k. 
    s,ts,t indicates the interval and k indicates the kth big number in interval s,ts,tOutputFor each test case, output m lines. Each line contains the kth big number.Sample Input

    1 
    10 1 
    1 4 2 3 5 6 7 8 9 0 
    1 3 2 

    Sample Output

    2 

    题意很简单,就是给你n个数,再给你m组询问,问区间[l,r]中第k小的数字是多少。

    我们可以先考虑简单情况,假设l和r固定是1和n,要怎么做。不考虑离线版本,如果强制在线的话我们可以建一棵权值线段树,就是维护数字数量的线段树,比如说,有五个数字1,1,2,2,2,那么Tree[1]=2,Tree[2]=3,这样假设我要查询第k小的数,可以选择二分区域,找到sum[T[1~pos]]>=k,那pos就是第k小的数字了。这种方法复杂度是logn*logn,我们可以再优化一下。可以从线段树的根节点开始找,每次向下查找,如果左节点的数字总数大于等于k,说明第k小的数字一定在左节点,我们就往左节点走,否则往右节点走,同时k要减去左节点数字总数,因为线段树维护的不是前缀和,右节点数字总数不包括左节点数字总数。这样不断递归下去,当l==r的时候返回l就行了。这种查询方式复杂度是logn。注意,当数字太大时要进行离散化再建树,否则内存会爆炸。

    接下来考虑进化版,l和r是任意数字的时候,我们可以考虑建n棵线段树,SegTree[i]代表插入第i个数字的时候对应的线段树,假设我能得到这n棵线段树,那我查询[l,r]这区间的第k小数字可以利用前缀和用第r棵线段树减去第l-1棵线段树,此时可以得到[l,r]这部分的线段树,然后我们再查询第k小的时候可以用上面讲过的方法查询。关键在于,如果真的建了n棵线段树,内存会爆炸。其实仔细思考我们会发现,当我们添加了一个点后,会影响到线段树的节点只有logn个,我们只需要新建这logn个节点就好,这样空间复杂度就大大降低了。我们用root[i]代表SegTree[i]的根节点,在更新的时候只更新被影响到的logn个节点就好。具体实现看代码,有注释。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include <string>
    #include <stack>
    #include <map>
    #include <set>
    #include <bitset>
    #define X first
    #define Y second
    #define clr(u,v); memset(u,v,sizeof(u));
    #define in() freopen("data","r",stdin);
    #define out() freopen("ans","w",stdout);
    #define Clear(Q); while (!Q.empty()) Q.pop();
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef pair<int, int> pii;
    const int maxn = 1e5 + 10;
    const int INF = 0x3f3f3f3f;
    struct Tree
    {
        int l, r, sum; //l,r指向左右子树,sum该节点对应的数字的总数量
    } T[maxn<<5]; //这是一颗权值线段树,就是根据数字建树,维护数字的个数
    
    int cnt;//线段树总节点数
    int N[maxn];
    int root[maxn];//root[i]代表第i棵线段树所在位置
    vector <int> V;
    
    void init()
    {
        cnt = 0;
        V.clear();
        T[0].sum = 0;
    }
    
    int id(int x)//用于离散
    {
        return lower_bound(V.begin(), V.end(), x) - V.begin() + 1;
    }
    
    void update(int l, int r, int &x, int y, int pos)
    {
        T[++cnt] = T[y]; //新建节点,并继承他的上一个版本
        T[cnt].sum++;//新加了一个数字,sum++
        x = cnt;//将这节点和他的父节点连起来
        if (l == r) return ;
        int mid = (l + r) >> 1;
        if (mid >= pos) update(l, mid, T[x].l, T[y].l, pos);//折半查找找到pos的位置进行更新,线段树的基本操作
        else update(mid + 1, r, T[x].r, T[y].r, pos);
    }
    
    int query(int l, int r, int x, int y, int k) //查询第k大
    {
        if (l == r) return l;
        int mid = (l + r) >> 1;
        int sum = T[T[y].l].sum - T[T[x].l].sum;//利用前缀和求出[x,y]这部分的线段树
        //T[T[y].l].sum - T[T[x].l].sum可以得出[x,y]这线段树左子树的sum
        if (sum >= k) return query(l, mid, T[x].l, T[y].l, k);//如果sum>=k说明要找的值在左子树
        else return query(mid + 1, r, T[x].r, T[y].r, k - sum);//否则到右子树去找
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while (T--)
        {
            init();
            int n, m, l, r, k;
            scanf("%d%d", &n, &m);
            for (int i = 1; i <= n; i++)
            {
                scanf("%d", &N[i]);
                V.pb(N[i]);
            }
            sort(V.begin(), V.end());
            V.erase(unique(V.begin(), V.end()), V.end());
            for (int i = 1; i <= n; i++)
                update(1, n, root[i], root[i-1], id(N[i]));//根据1~n建立n棵线段树,root[i]代表前i棵线段树的根节点,root[i-1]则是上一个版本的线段树
            while (m--)
            {
                scanf("%d%d%d", &l, &r, &k);
                printf("%d
    ", V[query(1, n, root[l-1], root[r], k) - 1]); //查询[l,r]这棵线段树的第k小数
            }
        }
        return 0;
    }
  • 相关阅读:
    关于 log4j.additivity
    JDK8新特性:使用Optional:解决NPE问题的更干净的写法
    异常处理和日志输出使用小结
    搭建DNS服务器
    git 使用技巧
    mysql
    linux学习记录
    nginx解析
    node npm pm2命令简析
    jenkins使用简析
  • 原文地址:https://www.cnblogs.com/scaugsh/p/6859240.html
Copyright © 2011-2022 走看看