★★☆ 输入文件:longterm_segtree.in 输出文件:longterm_segtree.out 简单对比
时间限制:3 s 内存限制:256 MB
【题目描述】
为什么说本题是福利呢?因为这是一道非常直白的可持久化线段树的练习题,目的并不是虐人,而是指导你入门可持久化数据结构。
线段树有个非常经典的应用是处理RMQ问题,即区间最大/最小值询问问题。现在我们把这个问题可持久化一下:
Q k l r 查询数列在第k个版本时,区间[l, r]上的最大值
M k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本
最开始会给你一个数列,作为第1个版本。
每次M操作会导致产生一个新的版本。修改操作可能会很多呢,如果每次都记录一个新的数列,空间和时间上都是令人无法承受的。所以我们需要可持久化数据结构:
对于最开始的版本1,我们直接建立一颗线段树,维护区间最大值。
修改操作呢?我们发现,修改只会涉及从线段树树根到目标点上一条树链上logn个节点而已,其余的节点并不会受到影响。所以对于每次修改操作,我们可以只重建修改涉及的节点即可。就像这样:
需要查询第k个版本的最大值,那就从第k个版本的树根开始,像查询普通的线段树一样查询即可。
要计算好所需空间哦
【输入格式】
第一行两个整数N, Q。N是数列的长度,Q表示询问数
第二行N个整数,是这个数列
之后Q行,每行以0或者1开头,0表示查询操作Q,1表示修改操作M,格式为
0 k l r 查询数列在第k个版本时,区间[l, r]上的最大值 或者
1 k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本
【输出格式】
对于每个M询问,输出正确答案
【样例输入】
4 5
1 2 3 4
0 1 1 4
1 1 3 5
0 2 1 3
0 2 4 4
0 1 2 4
【样例输出】
4
5
4
4
【提示】
序列版本1: 1 2 3 4
查询版本1的[1, 4]最大值为4
修改产生版本2: 1 2 5 4
查询版本2的[1, 3]最大值为5
查询版本1的[4, 4]最大值为4
查询版本1的[2, 4]最大值为4
数据范围
N <= 10000 Q <= 100000
对于每次询问操作的版本号k保证合法,
区间[l, r]一定满足1 <= l <= r <= N
【来源】
lj出题人: sxysxy。原题见: http://syzoj.com/problem/247
思路:可持久化线段树
思路:裸的主席树 你需要一个线段树,支持查询操作; 你还需要一个链表,储存每一个状态; 每次查询时,更新路径上的有用值就行了。 不要在意线段树上有什么,你所有的状态都存在链表里。 代码实现: 1 #include<cstdio> 2 const int maxn=1e4+10; 3 const int maxq=1e5+10; 4 int n,q,cs,id=1; 5 int a,b,c,d; 6 struct tree{int s,n;}tr[maxn<<2]; 7 int tm[maxq]; 8 struct change{int s,ln,rn;}cg[maxq<<5]; 9 inline int min_(int x,int y){return x<y?x:y;} 10 inline int max_(int x,int y){return x>y?x:y;} 11 void make_t(int t,int l,int r,int k){ 12 if(l==r){ 13 scanf("%d",&tr[k].s); 14 cg[++cs].s=tr[k].s,tr[k].n=cs; 15 return; 16 } 17 int mid=l+r>>1,ls=k<<1,rs=ls+1; 18 make_t(t,l,mid,ls); 19 make_t(t,mid+1,r,rs); 20 tr[k].s=max_(tr[ls].s,tr[rs].s); 21 cg[++cs]=(change){tr[k].s,tr[ls].n,tr[rs].n},tr[k].n=cs; 22 } 23 void change_t(int t,int l,int r,int k,int p,int f){ 24 if(l==r){ 25 tr[k].s=d; 26 cg[++cs].s=tr[k].s,tr[k].n=cs; 27 return; 28 } 29 int mid=l+r>>1,ls=k<<1,rs=ls+1; 30 if(cg[f].ln) tr[ls].s=cg[cg[f].ln].s,tr[ls].n=cg[f].ln; 31 if(cg[f].rn) tr[rs].s=cg[cg[f].rn].s,tr[rs].n=cg[f].rn; 32 if(p<=mid) change_t(t,l,mid,ls,p,cg[f].ln); 33 else change_t(t,mid+1,r,rs,p,cg[f].rn); 34 tr[k].s=max_(tr[ls].s,tr[rs].s); 35 cg[++cs]=(change){tr[k].s,tr[ls].n,tr[rs].n},tr[k].n=cs; 36 } 37 int sum_t(int l,int r,int k,int al,int ar,int f){ 38 if(l==al&&r==ar) return tr[k].s; 39 int mid=l+r>>1,ls=k<<1,rs=ls+1,ans=0; 40 if(cg[f].ln) tr[ls].s=cg[cg[f].ln].s; 41 if(cg[f].rn) tr[rs].s=cg[cg[f].rn].s; 42 if(al<=mid) ans=max_(ans,sum_t(l,mid,ls,al,min_(ar,mid),cg[f].ln)); 43 if(ar>mid) ans=max_(ans,sum_t(mid+1,r,rs,max_(al,mid+1),ar,cg[f].rn)); 44 return ans; 45 } 46 int main(){ 47 freopen("longterm_segtree.in","r",stdin); 48 freopen("longterm_segtree.out","w",stdout); 49 scanf("%d%d",&n,&q); 50 make_t(1,1,n,1); 51 tm[1]=tr[1].n; 52 while(q--){ 53 scanf("%d%d%d%d",&a,&b,&c,&d); 54 if(a){ 55 change_t(++id,1,n,1,c,tm[b]); 56 tm[id]=tr[1].n; 57 } 58 else printf("%d\n",sum_t(1,n,1,c,d,tm[b])); 59 } 60 return 0; 61 } R了两遍,一直在找链表的锅,最后发现线段树开小了。。。我内心有点复杂。