可持久化线段树--主席树

4 篇文章 0 订阅
4 篇文章 0 订阅

洛谷P3834
题目背景
这是个非常经典的可持久化权值线段树入门题——静态区间第 kk 小。

数据已经过加强,请使用可持久化权值线段树。同时请注意常数优化。

题目描述
如题,给定 nn 个整数构成的序列 aa,将对于指定的闭区间 [l, r][l,r] 查询其区间内的第 kk 小值。

输入格式
第一行包含两个整数,分别表示序列的长度 nn 和查询的个数 mm。
第二行包含 nn 个整数,第 ii 个整数表示序列的第 ii 个元素 a_ia
i


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

输出格式
对于每次询问,输出一行一个整数表示答案。

输入输出样例
输入 #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
说明/提示
样例 1 解释
n=5n=5,数列长度为 55,数列从第一项开始依次为{25957, 6405, 15770, 26287, 26465}{25957,6405,15770,26287,26465}。

第一次查询为 [2, 2][2,2] 区间内的第一小值,即为 64056405。
第二次查询为 [3, 4][3,4] 区间内的第一小值,即为 1577015770。
第三次查询为 [4, 5][4,5] 区间内的第一小值,即为 2628726287。
第四次查询为 [1, 2][1,2] 区间内的第二小值,即为 2595725957。
第五次查询为 [4, 4][4,4] 区间内的第一小值,即为 2628726287。
数据规模与约定
对于 100%100% 的数据,满足 1 ≤n,m≤2e5 ,∣a i ∣≤1e9,1≤l≤r≤n,1≤k≤r−l+1。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+100;
struct node{
    int sum,l,r;
}tr[N<<5];
int root[N],a[N],n,m,tot;
vector<int> v;
int getid(int x){
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void insert(int l,int r,int pre,int &now,int p){
    tr[++tot]=tr[pre];
    now=tot;
    tr[now].sum++;
    int mid = (l+r)>>1;
    if(l==r)
        return;
    if(p<=mid)
        insert(l,mid,tr[pre].l,tr[now].l,p);
    else
        insert(mid+1,r,tr[pre].r,tr[now].r,p);
}
int query(int l,int r,int L,int R,int k){
    if(l==r) return l;
    int mid = (l+r)>>1;
    int tmp = tr[tr[R].l].sum-tr[tr[L].l].sum;
    if(k<=tmp)
        return query(l,mid,tr[L].l,tr[R].l,k);
    else
        return query(mid+1,r,tr[L].r,tr[R].r,k-tmp);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",a+i);
        v.push_back(a[i]);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i = 1;i<=n;i++){
        insert(1,n,root[i-1],root[i],getid(a[i]));
    }
    while(m--){
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        int x = query(1,n,root[l-1],root[r],k);
        printf("%d\n",v[x-1]);
    }



    return 0;
}

洛谷P3919
题目背景
UPDATE : 最后一个点时间空间已经放大

2021.9.18 增添一组 hack 数据 by @panyf

标题即题意

有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)

题目描述
如题,你需要维护这样的一个长度为 NN 的数组,支持如下几种操作

在某个历史版本上修改某一个位置上的值

访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

输入格式
输入的第一行包含两个正整数 N, MN,M, 分别表示数组的长度和操作的个数。

第二行包含NN个整数,依次为初始状态下数组各位的值(依次为 a_ia
i

,1 \leq i \leq N1≤i≤N)。

接下来MM行每行包含3或4个整数,代表两种操作之一(ii为基于的历史版本号):

对于操作1,格式为v_i \ 1 \ {loc}_i \ {value}iv
i

1 loc
i

value
i

,即为在版本v_iv
i

的基础上,将 a
{{loc}_i}a
loc
i


修改为 {value}_ivalue
i

对于操作2,格式为v_i \ 2 \ {loc}iv
i

2 loc
i

,即访问版本v_iv
i

中的 a
{{loc}_i}a
loc
i


的值,生成一样版本的对象应为vi

输出格式
输出包含若干行,依次为每个操作2的结果。

输入输出样例
输入 #1复制
5 10
59 46 14 87 41
0 2 1
0 1 1 14
0 1 1 57
0 1 1 88
4 2 4
0 2 5
0 2 4
4 2 1
2 2 2
1 1 5 91
输出 #1复制
59
87
41
87
88
46
说明/提示
数据规模:

对于30%的数据:1 \leq N, M \leq {10}^31≤N,M≤10
3

对于50%的数据:1 \leq N, M \leq {10}^41≤N,M≤10
4

对于70%的数据:1 \leq N, M \leq {10}^51≤N,M≤10
5

对于100%的数据:1 \leq N, M \leq {10}^6, 1 \leq {loc}_i \leq N, 0 \leq v_i < i, -{10}^9 \leq a_i, {value}_i \leq {10}^91≤N,M≤10
6
,1≤loc
i

≤N,0≤v
i

<i,−10
9
≤a
i

,value
i

≤10
9

经测试,正常常数的可持久化数组可以通过,请各位放心

数据略微凶残,请注意常数不要过大

另,此题I/O量较大,如果实在TLE请注意I/O优化

询问生成的版本是指你访问的那个版本的复制

样例说明:

一共11个版本,编号从0-10,依次为:

  • 0 : 59 46 14 87 41

  • 1 : 59 46 14 87 41

  • 2 : 14 46 14 87 41

  • 3 : 57 46 14 87 41

  • 4 : 88 46 14 87 41

  • 5 : 88 46 14 87 41

  • 6 : 59 46 14 87 41

  • 7 : 59 46 14 87 41

  • 8 : 88 46 14 87 41

  • 9 : 14 46 14 87 41

  • 10 : 59 46 14 87 91

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
struct node
{
	int val,l,r;
}tr[N<<5];
int root[N<<5],tot,n,m;
void build(int i,int l,int r){
	if(l==r){
		scanf("%d",&tr[i].val);
		return;
	} 
	int mid = l+r>>1;
	build(tr[i].l=++tot,l,mid);
	build(tr[i].r=++tot,mid+1,r);
}
int query(int i,int l,int r,int p){
	if(l==r){
		return tr[i].val;
	}
	int mid = l+r>>1;
	if(p<=mid){
		return query(tr[i].l,l,mid,p);
	}
	else{
		return query(tr[i].r,mid+1,r,p);
	}
}
void update(int pre,int &now,int l,int r,int p,int x){
	tr[now=++tot]=tr[pre];
	int mid = l+r>>1;
	if(l==r){
		tr[now].val = x;
		return;
	}
	if(p<=mid){
		update(tr[pre].l,tr[now].l,l,mid,p,x);
	}
	else{
		update(tr[pre].r,tr[now].r,mid+1,r,p,x);
	}

}

int main()
{
	scanf("%d%d",&n,&m);
	build(root[0]=++tot,1,n);
	for(int ver=1;ver<=m;ver++){
		int pre,op;
		scanf("%d%d",&pre,&op);
		if(op==2){
			int i;scanf("%d",&i);
			printf("%d\n",query(root[pre],1,n,i));
			root[ver]=root[pre];
		}
		else {
			int i, x;
			scanf("%d%d",&i,&x);
			update(root[pre],root[ver],1,n,i,x);
		}
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值