洛谷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;
}