【刷题】洛谷 P3834 【模板】可持久化线段树 1(主席树)

题目背景

这是个非常经典的主席树入门题——静态区间第K小

数据已经过加强,请使用主席树。同时请注意常数优化

题目描述

如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入输出格式

输入格式:

第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。

第二行包含N个正整数,表示这个序列各项的数字。

接下来M行每行包含三个整数l, r, kl,r,k , 表示查询区间[l, r][l,r] 内的第k小值。

输出格式:

输出包含k行,每行1个正整数,依次表示每一次查询的结果

输入输出样例

输入样例#1:

5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1

输出样例#1:

6405
15770
26287
25957
26287

说明

数据范围

对于20%的数据满足:\(1 \leq N, M \leq 10\)

对于50%的数据满足:\(1 \leq N, M \leq 10^3\)

对于80%的数据满足:\(1 \leq N, M \leq 10^5\)

对于100%的数据满足:\(1 \leq N, M \leq 2\cdot 10^5\)

对于数列中的所有数\(a_i\)? ,均满足\(-{10}^9 \leq a_i \leq {10}^9\)

样例数据说明

N=5,数列长度为5,数列从第一项开始依次为\([25957, 6405, 15770, 26287, 26465 ]\)

第一次查询为\([2, 2]\) 区间内的第一小值,即为6405

第二次查询为\([3, 4]\) 区间内的第一小值,即为15770

第三次查询为\([4, 5]\) 区间内的第一小值,即为26287

第四次查询为\([1, 2]\) 区间内的第二小值,即为25957

第五次查询为\([4, 4]\) 区间内的第一小值,即为26287

题解

主席树
这道题当作是我学习主席树的开端吧,模板题
主席树维护的是区间,但这个区间不是下标(即位置)的区间,而是权值的区间
在一个版本中

  • 区间\([l,l]\)上记录的是\(l\)这个数出现了多少次
  • 区间\([l,r]\)上记录的就是\(l,l+1,...,r-1,r\)每个数出现次数之和了

所以要先离散化
主席树说白了就是给一个长度为\(n\)的序列建\(n\)棵权值线段树,第\(i\)棵线段树只存了\(a_1,a_2,a_3,...a_{i-1},a_i\)
这样我们对于本题这样的静态查询第\(k\)小,就可以差分了(想一想原理)
然后发现相邻两棵线段树改变的只有一条路径上的值,其它的值都是一样的,那么为了节省空间,我们把那些值一样的存在同一个节点(即共用节点),就不用再开节点了
同时,我们称第\(i\)棵线段树为第\(i\)个版本
差分的话,就是
如果要查询\([l,r]\)中的信息,那么我们看
第\(r\)棵线段树记录的是\(a_1,a_2,...,a_r\)中的信息
第\(l-1\)棵线段树记录的是\(a_1,a_2,...a_{l-1}\)中的信息
它们相减后,就变成\(a_l,a_{l+1},...,a_r\)中的信息了
而它们能够相减的依据是:每一棵线段树每一个节点维护的内容是一样的(只是其中的值不一样而已)

#include<bits/stdc++.h>
#define ll long long
#define db double
#define ld long double
#define Mid ((l+r)>>1)
#define lson l,Mid
#define rson Mid+1,r
const int MAXN=200000+10;
int n,m,A[MAXN];
struct ChairMan_Tree{
    int sum[MAXN<<5],lc[MAXN<<5],rc[MAXN<<5],cnt,root[MAXN];
    inline void init()
    {
        memset(sum,0,sizeof(sum));
        memset(lc,0,sizeof(lc));
        memset(rc,0,sizeof(rc));
        cnt=0;
    }
    inline void Build(int &rt,int l,int r)
    {
        rt=++cnt;
        sum[rt]=0;
        if(l==r)return ;
        Build(lc[rt],lson);
        Build(rc[rt],rson);
    }
    inline void Insert(int &rt,int l,int r,int last,int pos)
    {
        rt=++cnt;
        lc[rt]=lc[last];
        rc[rt]=rc[last];
        sum[rt]=sum[last]+1;
        if(l==r)return ;
        else
        {
            if(pos<=Mid)Insert(lc[rt],lson,lc[last],pos);
            else Insert(rc[rt],rson,rc[last],pos);
        }
    };
    inline int Query(int now,int last,int l,int r,int k)
    {
        if(l==r)return l;
        else
        {
            int t=sum[lc[now]]-sum[lc[last]];
            if(k<=t)return Query(lc[now],lc[last],lson,k);
            else return Query(rc[now],rc[last],rson,k-t);
        }
    };
};
ChairMan_Tree T;
std::vector<int> V;
std::map<int,int> M;
template<typename T> inline void read(T &x)
{
    T data=0,w=1;
    char ch=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0'&&ch<='9')data=((T)data<<3)+((T)data<<1)+(ch^'0'),ch=getchar();
    x=data*w;
}
template<typename T> inline void write(T x,char c='\0')
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)write(x/10);
    putchar(x%10+'0');
    if(c!='\0')putchar(c);
}
template<typename T> inline void chkmin(T &x,T y){x=(y<x?y:x);}
template<typename T> inline void chkmax(T &x,T y){x=(y>x?y:x);}
template<typename T> inline T min(T x,T y){return x<y?x:y;}
template<typename T> inline T max(T x,T y){return x>y?x:y;}
inline void discre()
{
    sort(V.begin(),V.end());
    V.erase(unique(V.begin(),V.end()),V.end());
    for(register int i=1;i<=n;++i)
    {
        int pre=A[i];
        A[i]=lower_bound(V.begin(),V.end(),A[i])-V.begin()+1;
        M[A[i]]=pre;
    }
}
int main()
{
    read(n);read(m);
    T.init();
    T.Build(T.root[0],1,n);
    for(register int i=1;i<=n;++i)
    {
        read(A[i]);
        V.push_back(A[i]);
    }
    discre();
    for(register int i=1;i<=n;++i)T.Insert(T.root[i],1,n,T.root[i-1],A[i]);
    while(m--)
    {
        int l,r,k;
        read(l);read(r);read(k);
        write(M[T.Query(T.root[r],T.root[l-1],1,n,k)],'\n');
    }
    return 0;
}

原文地址:https://www.cnblogs.com/hongyj/p/8585945.html

时间: 2024-11-07 21:57:33

【刷题】洛谷 P3834 【模板】可持久化线段树 1(主席树)的相关文章

洛谷.3834.[模板]可持久化线段树(主席树 静态区间第k小)

题目链接 //离散化后范围1~cnt不要错 #include<cstdio> #include<cctype> #include<algorithm> //#define gc() getchar() #define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++) const int N=2e5+5,MAXIN=2e6; int n,m,A[N],ref[N],cn

【洛谷P1168】中位数(Splay)/(主席树)

Description 给出一个长度为N的非负整数序列A[i],对于所有1 ≤ k ≤ (N + 1) / 2,输出A[1], A[2], -, A[2k - 1]的中位数.即前1,3,5,--个数的中位数. N ≤ 100000 Solution 这题方法很多,这里介绍splay的打法 求中位数即求第$(k+1)/$2小的数,用splay维护即可,只有2中操作:插入,旋转 在树上记录一个\(c(u)\)表示节点\(u\)的子树有几个节点,用来判断第n小 只要在插入和旋转的时候维护就行了 Cod

洛谷3919:可持久化数组——题解

https://www.luogu.org/problemnew/show/P3919 如题,你需要维护这样的一个长度为 N 的数组,支持如下几种操作 在某个历史版本上修改某一个位置上的值 访问某个历史版本上的某一位置的值 此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本.版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组) 这题题意看错了就很伤……操作2新建的版本是它所询问的历史版本emmm…… 以及各种小错误,int没retur

归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k大 ,,,, 这个问题的通用算法是 划分树,, 说白一点就是把快速排序的中间结果存起来, 举个栗子 原数列 4 1 8 2 6 9 5 3 7 sorted 1 2 3 4 5 6 7 8 9 ........................... qs[0] 4 1 8 2 6 9 5 3 7 q

洛谷P1531 I Hate It 线段树

洛谷P1531 I Hate It 线段树 单点修改 区间求最大 1 #include <cstdio> 2 #include <cmath> 3 #include <cstdlib> 4 #include <cstring> 5 #include <string> 6 #include <algorithm> 7 #include <iomanip> 8 #include <iostream> 9 using

[可持久化线段树(主席树)]

主席树 抛出问题 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个整数,表示这个序列各项的数字. 接下来M行每行包含三个整数l, r, kl,r,k , 表示查询区间[l, r][l,r]内的第k小值. 输出格式: 输出包含k行,每行1个整数,依次表示每一次查询的结果 解决问题 主席树(可持久化线段树)法 于是针对这个问题,新的数据结构诞生了,也就是主席树. 主席树本名

AC自动机(附洛谷P3769模板题)

首先,介绍一下AC自动机(Aho-Corasick automaton),是一种在一个文本串中寻找每一个已给出的模式串的高效算法. 在学习AC自动机之前,你需要先学习Trie树和KMP算法,因为AC自动机正式利用并结合了两者的思想. 说到实际的不同,其实AC自动机只是在Trie树上引入了一个类似KMP中next数组的东西叫做Fail指针. 对于每一个节点,Fail指针指向该节点所代表的字符串中,次长的.在Trie树中存在的后缀(因为最长的在Trie树种存在的后缀就是其本身)所代表的节点. 举例:

【C++】最近公共祖先LCA(Tarjan离线算法)&amp;&amp; 洛谷P3379LCA模板

1.前言 首先我们介绍的算法是LCA问题中的离线算法-Tarjan算法,该算法采用DFS+并查集,再看此算法之前首先你得知道并查集(尽管我相信你如果知道这个的话肯定是知道并查集的),Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解(个人认为). 2.思想 下面详细介绍一下Tarjan算法的思想: 1.任选一个点为根节点,从根节点开始. 2.遍历该点u所有子节点v,并标记这些子节点v已被访问过. 3.若是v还有子节点,返回2,否则下一步. 4.合并v到u上. 5.寻找与当前点

洛谷P3375 [模板]KMP字符串匹配

To 洛谷.3375 KMP字符串匹配 题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next.如果你不知道这是什么意思也不要问,去百度搜[kmp算法]学习一下就知道了. 输入输出格式 输入格式: 第一行为一个字符串,即为s1(仅包含大写字母) 第二行为一个字符串,即为s2(仅包含大写字母) 输出格式: 若干行,每行包含一个整数,表示s2在s1中出现的位置 接下来1行,包括length(s2)个整