标签:orm 合并 定义 char ++ 必须 turn rev block
好一道神仙题
思路来源于yyb神仙
题目要求我们支持4个操作:
区间赋值
区间加
区间最大值
区间历史最大值
这第四个操作简直是BUG一样的存在
思考一下,发现难点在于维护区间历史最大值
先打完这些:
#include<bits/stdc++.h> using namespace std; const int N=100005; #define INF 1050000000 struct TAG{ #define il inline int a,b; il void clear(){a=0,b=-INF;} il int number(int x){return max(a+x,b);} }; int n,m; int a[N]; struct Sugment_Tree{//0:目前最大值 1:历史最大值 #define mid (l+r)/2 #define il inline Sugment_Tree(){ } il void push_up(int num){ } il void put_tag(int num,TAG a,TAG b){ } il void push_down(int num){ } il void build(int l,int r,int num){ if(l==r){ } build(l,mid,num<<1); build(mid+1,r,num<<1|1); push_up(num); } il void change(int l,int r,int num,int L,int R,TAG X){ if(l>R||r<L) return; if(l>=L&&r<=R){ return; } push_down(num); change(l,mid,num<<1,L,R,X); change(mid+1,r,num<<1|1,L,R,X); } il int ask(int l,int r,int num,int L,int R,int opt){ if(l>R||r<L) return 0; if(l>=L&&r<=R){ } push_down(num); return max(ask(l,mid,num<<1,L,R,opt),ask(mid+1,r,num<<1|1,L,R,opt)); } }T; char s[5]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); T.build(1,n,1); scanf("%d",&m); while(m--){ int l,r,X; scanf("%s",s); if(s[0]==‘Q‘){ scanf("%d%d",&l,&r); int p=T.ask(1,n,1,l,r,0); printf("%d\n",p); } else if(s[0]==‘A‘){ scanf("%d%d",&l,&r); int p=T.ask(1,n,1,l,r,1); printf("%d\n",p); } else if(s[0]==‘P‘){ scanf("%d%d%d",&l,&r,&X); TAG p; p.clear(); p.a=X; T.change(1,n,1,l,r,p); } else{ scanf("%d%d%d",&l,&r,&X); TAG p; p.clear(); p.b=X; T.change(1,n,1,l,r,p); } } return 0; }
记住,inf不能太大,否则待会会爆int
然后开始一步一步想。
先给出神仙思路:
定义一个表示(a,b)表示区间内所有数先+a再和b取max,即x=max(x+a,b)
那么这样一来,区间加法转化成(a,−∞),区间赋值变成了(−∞,b)
这个思路的好处是将两种操作当成了一种来做
理论上,我们对每一个区间只要维护区间目前最大值、区间历史最大值、区间标记即可
但是历史最值还是维护不了。
考虑如何维护一个历史最值,我们对于每个点额外维护一个历史最值的标记,每次覆盖的时候不会覆盖掉历史最值的标记,只会用max操作更新历史最值标记。
接下来分段看
il void push_up(int num){ t[num]=max(t[num<<1],t[num<<1|1]); hm[num]=max(hm[num<<1],hm[num<<1|1]); }
这个push_up比较常规,不说
il void push_down(int num){ put_tag(num<<1,tag[num],mtg[num]); put_tag(num<<1|1,tag[num],mtg[num]); mtg[num].clear(); tag[num].clear(); }
不妨让我们来思考一下:
这是一个下传图示
假设现在num<<1,num<<1|1上没有标记,num要下传
我们分别调用push_tag函数,合并标记
il void put_tag(int num,TAG a,TAG b){ mtg[num]=max(mtg[num],tag[num]+b); tag[num]=tag[num]+a; hm[num]=max(hm[num],b.number(t[num])); t[num]=a.number(t[num]); }
精髓部分
第一行:mtg代表历史标记最值
这一行里我们所做的是维护历史最值标记
tag是当前标记
b是父亲节点的mtg
要是想要更新num的mtg,那么父亲节点必须选取mtg才最有可能
然后就有了第一行
+和max等下再说
第二行:tag代表目前标记
我们只需要把现在父亲传给num的标记合并上即可
第三行:hm表示历史最值
t表示当前最值
我们考虑,b是父亲的历史最值标记
这个标记有可能之前被修改了
b.number(x)代表x经过b操作之后的值与x比的较大值
说白了,就是试试看现在的num历史最值和当前num最值+b操作之后会不会改变历史
第四行:直接看看t会不会改变
TAG max(TAG x,TAG y){ return (TAG){max(x.a,y.a),max(x.b,y.b)}; } TAG operator+ (TAG x,TAG y){ return (TAG){max(-INF,x.a+y.a),max(x.b+y.a,y.b)}; }
这两行就是标记的max与+
可以发现,max实际上就是最大的+拼上最大的max
比如:
操作 A(1,3) B(-2,6)
当前数组为1 4 2
什么都不做:最大值4 历史最大值4
做了A之后:最大值5 历史最大值5
做了B之后:最大值6 历史最大值6
对于两个标记如何合并max,是(max(a,x),max(b,y))。原因就是我们可以把这个标记看做是一个分段函数,那么这个合并就比较显然了。
那么取max后就是C(1,6)
(原因:max很好理解,而+是迟早要加的)
合并:
考虑先后顺序,加法并一起,max的话,分两种:先变max再加x或者直接变max(sum,y)
原因就是注意一下maxmax和加法的先后顺序。
il void change(int l,int r,int num,int L,int R,TAG X){ if(l>R||r<L) return; if(l>=L&&r<=R){ put_tag(num,X,X); return; } push_down(num); change(l,mid,num<<1,L,R,X); change(mid+1,r,num<<1|1,L,R,X); push_up(num); }
线段树部分只有这个操作可说了。
为什么我们直接put_tag?
因为num的父亲也做了,然后拖到这一步做而已。
代码(完整)
#include<bits/stdc++.h> using namespace std; const int N=100005; #define INF 1050000000 struct TAG{ #define il inline int a,b; il void clear(){a=0,b=-INF;} il int number(int x){return max(a+x,b);} }; int n,m; int a[N]; TAG max(TAG x,TAG y){ return (TAG){max(x.a,y.a),max(x.b,y.b)}; } TAG operator+ (TAG x,TAG y){ return (TAG){max(-INF,x.a+y.a),max(x.b+y.a,y.b)}; } struct Sugment_Tree{//0:目前最大值 1:历史最大值 #define mid (l+r)/2 #define il inline TAG mtg[N<<2];//历史最值标记,每次覆盖的时候不会覆盖掉历史最值的标记,只会用max操作更新历史最值标记。 TAG tag[N<<2];//普通标记 int t[N<<2];//目前最值 int hm[N<<2];//历史最值 il void push_up(int num){ t[num]=max(t[num<<1],t[num<<1|1]); hm[num]=max(hm[num<<1],hm[num<<1|1]); } il void put_tag(int num,TAG a,TAG b){ mtg[num]=max(mtg[num],tag[num]+b); tag[num]=tag[num]+a; hm[num]=max(hm[num],b.number(t[num])); t[num]=a.number(t[num]); } il void push_down(int num){ put_tag(num<<1,tag[num],mtg[num]); put_tag(num<<1|1,tag[num],mtg[num]); mtg[num].clear(); tag[num].clear(); } il void build(int l,int r,int num){ mtg[num].clear(); tag[num].clear(); if(l==r){ t[num]=hm[num]=a[l]; return; } build(l,mid,num<<1); build(mid+1,r,num<<1|1); push_up(num); } il void change(int l,int r,int num,int L,int R,TAG X){ if(l>R||r<L) return; if(l>=L&&r<=R){ put_tag(num,X,X); return; } push_down(num); change(l,mid,num<<1,L,R,X); change(mid+1,r,num<<1|1,L,R,X); push_up(num); } il int ask(int l,int r,int num,int L,int R,int opt){ if(l>R||r<L) return -INF; if(l>=L&&r<=R){ return opt?hm[num]:t[num]; } push_down(num); return max(ask(l,mid,num<<1,L,R,opt),ask(mid+1,r,num<<1|1,L,R,opt)); } }T; char s[5]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); T.build(1,n,1); scanf("%d",&m); while(m--){ // printf("%d &&&&&&&&&&&&&&&&&\n",T.hm[15]); int l,r,X; scanf("%s",s); TAG p=(TAG){-INF,-INF}; if(s[0]==‘Q‘){ scanf("%d%d",&l,&r); int p=T.ask(1,n,1,l,r,0); printf("%d\n",p); } else if(s[0]==‘A‘){ scanf("%d%d",&l,&r); int p=T.ask(1,n,1,l,r,1); printf("%d\n",p); } else if(s[0]==‘P‘){ scanf("%d%d%d",&l,&r,&X); p.a=X; T.change(1,n,1,l,r,p); } else{ scanf("%d%d%d",&l,&r,&X); p.b=X; T.change(1,n,1,l,r,p); } } return 0; }
标签:orm 合并 定义 char ++ 必须 turn rev block
原文地址:https://www.cnblogs.com/QYJ060604/p/11748284.html