HDU2665 主席树入门

本文介绍了一种称为主席树的数据结构,这是一种可持续化的线段树,用于高效地处理数组的动态查询问题,如求解数组某一段内的第k个元素等。通过实例演示了主席树的构建过程和查询算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description
You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
That is, given an array a[1…n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: “What would be the k-th number in a[i…j] segment, if this segment was sorted?”
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2…5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.
Input
The first line of the input file contains n — the size of the array, and m — the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 10 9 by their absolute values — the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).
Output
For each question output the answer to it — the k-th number in sorted a[i…j] segment.
Sample Input
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
Sample Output
5
6
3

第一次接触主席树
这种树的全名叫做可持续化线段树
大体的意思就是把线段树形成的每一个历史记录都留下
因为是线段树,所以塞进去的是节点这点对树本身的结构是没有影响的
用shu这个数组来记录每一次修改的线段树的根节点
理论上来讲shu每一个节点都应该记录一整颗线段树
但是一定会MLE
所以采取一个优化策略那就是如果新的树的某个节点和原来的一样,那么就让他直接连接到上一课树的节点那里去
因此就出现了这样一种情况
在历史修改中任何一颗不一样的子树都必须存在
在这种情况下就用一个全局变量来记录开了第几棵树
其实就是数组模拟指针记录地址用的…
因为每次更新只加入一颗新节点,所以不修改的地方不需要的进行递归
这样每次修改应该也是log级的复杂度

#include<iostream>
#include<cmath>
#include<memory.h>
#include<cstdio>
#include<algorithm>
#include<string>
using namespace std;
int shuru[100001];
int tu[100001];
int zuo[2000000];
int you[2000000];
int su[2000000];
int shu[100000];
int tou = 0;
int jianli(int zu, int yo)
{
    int xianzai = ++tou;
    if (zu == yo)return xianzai;//根本不需要修改因为已经全部初始化为0了,其实应该更新成连接那颗空的树的
    int mid = (zu + yo) / 2;
    zuo[xianzai] = jianli(zu, mid);
    you[xianzai] = jianli(mid + 1, yo);
    return xianzai;
}
int gengxin(int shangyike, int weizhi, int zo, int yo)//其实执行递归的这个函数叫gengxin还不如叫jianyikexinde
{
    int xianzai = ++tou;
    if (zo == yo)
    {
        su[xianzai] = su[shangyike] + 1;//因为只有需要修改的那个节点才能递归到这里,其他的中间就死了
        return xianzai;//把这棵改了(新建)的子树位置返回去
    }
    int mid = (zo + yo) / 2;
    if (weizhi <= mid)
    {
        you[xianzai] = you[shangyike];//右边无所谓,那就是和以前的子树一样的
        zuo[xianzai] = gengxin(zuo[shangyike], weizhi, zo, mid);//左边不一样就得建新的
    }
    else
    {
        you[xianzai] = gengxin(you[shangyike], weizhi, mid + 1, yo);
        zuo[xianzai] = zuo[shangyike];
    }
    su[xianzai] = su[zuo[xianzai]] + su[you[xianzai]];//这个建的新的树就得收集一下两边的数据..
    return xianzai;//把自个的坐标返回,新的树这就信息更新全了
}
int wen(int qian, int hou, int zo, int yo, int k)//这个函数的作用是返回询问结果的
{
    if (zo == yo)return tu[zo];//和上面那个同理能走到这的肯定就是你想要的那个不然中间会死
    int shu = su[zuo[hou]] - su[zuo[qian]];//两棵树的差值...
    int mid = (zo + yo) / 2;
    if (shu >= k)return wen(zuo[qian], zuo[hou], zo, mid, k);//如果小了当然就从两棵树的左边开始找
    else return wen(you[qian], you[hou], mid + 1, yo, k - shu);//反之从两棵树的右边开始找
    //不管从哪边开始找那都无所谓,因为其他的地方都没改,就算去找了也肯定是一样的
    //本身这个查找的意思就是从两个区间建立起一颗区间的线段树区别只有一条路
}
int main()
{
    int n, m;
    int T;
    cin>>T;
    while (T--)
    {
        cin >> n >> m;
        tou = 0;
        memset(tu, 0, sizeof(tu));
        memset(shuru, 0, sizeof(shuru));
        memset(su, 0, sizeof(su));
        memset(shu, 0, sizeof(shu));
        memset(zuo, 0, sizeof(zuo));
        memset(you, 0, sizeof(you));
        for (int a = 1;a <= n;a++)
        {
            scanf("%d", &shuru[a]);
            tu[a] = shuru[a];
        }
        sort(tu + 1, tu + n + 1);
        int lisa = unique(tu + 1, tu + n + 1) - tu-1;
        shu[0] = jianli(1, lisa);
        for (int a = 1;a <= n;a++)
        {
            int weizhi = lower_bound(tu + 1, tu + 1 + lisa, shuru[a]) - tu;
            shu[a] = gengxin(shu[a - 1], weizhi, 1, lisa);
        }
        while (m--)
        {
            int q, w, e;
            cin >> q >> w >> e;
            cout << wen(shu[q-1], shu[w], 1, lisa, e) << endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值