一些奇妙的线段树操作

学过数据结构和会做题完全是两个概念orz

各种各样的题目都应该见识一下

简单的目录:

最大连续长度

吉司机线段树

线段树合并/分裂


 

最大连续长度问题

典型题目:HDU 3911 ($Black$ $And$ $White$)

题目大意:有一个长度为$n$($1\le n\le 10^5$)的$01$序列$a_i$,现在有$m$个操作:将区间$[l_i,r_i]$反转($01$互换),或询问区间$[l_i,r_i]$中$1$最多连续出现多少次

区间的反转很显然可以用懒标记解决,细节就不再赘述了(比如打完标记向上更新啥的一开始我就忘了orz)

难办的是区间内的最大连续长度,原因在于,两段小区间如何合并成一个大区间并不容易想到

先考虑最大连续长度可能的来源

  1. 仅在左半区间
  2. 仅在右半区间
  3. 横跨两个小区间

我们可以多记录一东西:除了当前区间内的最大连续长度,再记录当前区间内从最左端、最右端开始的最大连续长度

这样,如果我们想把两个小区间合并,得到的连续长度相当于是(左半区间从右端开始的最大长度+右半区间从左端开始的最大长度)

这两个新加的记录值也是容易合并的:求最左端开始的最大连续长度,如果左半区间全是相同数字,那么可以与右半区间的最左端合并,否则就是左半区间的最大连续长度;求最右端开始的亦是如此

所以本题的启示是,加入一些新的记录值来帮助计算

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
using namespace std;inline int max(int a,int b)
{return (a>b?a:b);
}
inline int min(int a,int b)
{return (a<b?a:b);
}
inline void swap(int &a,int &b)
{int tmp=a;a=b,b=tmp;
}const int MAX=100005;int n,m;
int c[MAX<<1];int sz;
int left[MAX<<2][2],right[MAX<<2][2],mid[MAX<<2][2];
int tag[MAX<<2];inline void Calc(int k,int a,int b)
{int len=(b-a+1)>>1;for(int i=0;i<2;i++){left[k][i]=left[k<<1][i]+(left[k<<1][i]==len?left[(k<<1)+1][i]:0);right[k][i]=right[(k<<1)+1][i]+(right[(k<<1)+1][i]==len?right[k<<1][i]:0);mid[k][i]=max(mid[k<<1][i],mid[(k<<1)+1][i]);//#1mid[k][i]=max(mid[k][i],right[k<<1][i]+left[(k<<1)+1][i]);}
}inline void Build(int k,int a,int b)
{if(a==b){left[k][c[a]]=right[k][c[a]]=mid[k][c[a]]=1;return;}Build(k<<1,a,(a+b)>>1);Build((k<<1)+1,((a+b)>>1)+1,b);Calc(k,a,b);
}inline void Update(int k,int a,int b)
{if(!tag[k])return;swap(left[k][0],left[k][1]);swap(right[k][0],right[k][1]);swap(mid[k][0],mid[k][1]);tag[k]=0;if(a!=b){tag[k<<1]^=1;tag[(k<<1)+1]^=1;}
}inline void Modify(int k,int l,int r,int a,int b)
{Update(k,a,b);if(a>r || b<l)return;if(a>=l && b<=r){tag[k]^=1;Update(k,a,b);return;}Modify(k<<1,l,r,a,(a+b)>>1);Modify((k<<1)+1,l,r,((a+b)>>1)+1,b);Calc(k,a,b);
}inline int Query(int k,int l,int r,int a,int b)
{Update(k,a,b);if(a>r || b<l)return 0;if(a>=l && b<=r)return mid[k][1];int half=(a+b)>>1;if(r<=half)return Query(k<<1,l,r,a,half);if(l>half)return Query((k<<1)+1,l,r,half+1,b);int midv=max(Query(k<<1,l,r,a,half),Query((k<<1)+1,l,r,half+1,b));midv=max(midv,min(right[k<<1][1],half-l+1)+min(left[(k<<1)+1][1],r-half));return midv;
}int main()
{
//    freopen("input.txt","r",stdin);while(~scanf("%d",&n)){memset(left,0,sizeof(left));memset(right,0,sizeof(right));memset(mid,0,sizeof(mid));memset(tag,0,sizeof(tag));memset(c,0,sizeof(c));for(int i=1;i<=n;i++)scanf("%d",&c[i]);sz=1;while(sz<n)sz<<=1;Build(1,1,sz);scanf("%d",&m);while(m--){int op,x,y;scanf("%d%d%d",&op,&x,&y);if(op==1)Modify(1,x,y,1,sz);elseprintf("%d\n",Query(1,x,y,1,sz));}}return 0;
}
View Code

 

吉司机线段树

这种做法有一种明显的标志:有一种操作是用$min(a_i,w)$来取代$a_i$

经典模板题:HDU 5306($Gorgeous$ $Sequence$)

感谢这篇题解:https://www.cnblogs.com/shenben/p/6641984.html

做法的精髓是,不仅记录 当前节点表示的区间中 的最大值$x$和区间和$sum$(不然只是普通的线段树了),同时记录区间中最大值的出现次数$cnt$严格次大值$y$

现在我们考虑如何处理题目中的$0$修改

首先通过递归,定位到某个严格包含在区间中的线段$t_k$,然后根据具体情况分为三种处理方式

  1. $t[k].x\le w$,即区间内的最大值也小于$w$,那么区间内的所有元素都不会改变,直接退出即可
  2. $t[k].y<w<t_k.x$,即区间内仅有最大值大于$w$,那么区间内的$t[k].cnt$个最大值都需要变成$w$,同时对$t[k].sum$做出更新($t[k].sum-=t[k].cnt\times (t[k].x-w)$,注意强制转换成long long
  3. $t[k].y\le w$,即区间内有超过一种值大于等于$w$(如果$t[k].y==w$归于第2类,那么$t[k].cnt$不会更新),无法直接更新,需要向下继续递归结束后再重新计算来更新

同时我们发现,所有的更新仅限于第$2$类,其并没有进行到底,所以每个节点的$t[k].x$都有类似懒标记的作用,在修改与查询的过程中应当将这个懒标记向下更新

这个做法的复杂度没找到推导...虽然有人说$O(NlogN)$,但还是觉得$O(N(logN)^2)$更靠谱点

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;typedef long long ll;
const int MAX=1000005;struct Node
{int x,y,cnt;ll sum;Node(int a=0,int b=0,int c=-1,ll d=0LL){x=a,y=b,cnt=c,sum=d;}
};int n,m;
int val[MAX<<1];
Node t[MAX<<2];inline void Update(int k)
{int left=k<<1,right=left+1;t[k].sum=t[left].sum+t[right].sum;if(t[left].x==t[right].x){t[k].x=t[left].x;t[k].y=max(t[left].y,t[right].y);t[k].cnt=t[left].cnt+t[right].cnt;}else{if(t[left].x<t[right].x)swap(left,right);t[k].x=t[left].x;t[k].y=max(t[left].y,t[right].x);t[k].cnt=t[left].cnt;}
}inline void Build(int k,int a,int b)
{if(a==b){t[k]=Node(val[a],-1,1,val[a]);return;}Build(k<<1,a,(a+b)>>1);Build((k<<1)+1,((a+b)>>1)+1,b);Update(k);
}inline void Dec(int k,int x)
{if(t[k].x<=x)return;t[k].sum-=(ll)t[k].cnt*(t[k].x-x);t[k].x=x;
}inline void Pushdown(int k)
{Dec(k<<1,t[k].x);Dec(k<<1|1,t[k].x);
}inline void Modify(int k,int l,int r,int a,int b,int w)
{if(a>r || b<l)return;if(a>=l && b<=r)//***
    {if(t[k].x<=w)return;if(t[k].y<w)//***
        {Dec(k,w);return;}}Pushdown(k);Modify(k<<1,l,r,a,(a+b)>>1,w);Modify(k<<1|1,l,r,((a+b)>>1)+1,b,w);Update(k);
}inline int Max(int k,int l,int r,int a,int b)
{if(a>r || b<l)return 0;if(a>=l && b<=r)return t[k].x;Pushdown(k);return max(Max(k<<1,l,r,a,(a+b)>>1),Max(k<<1|1,l,r,((a+b)>>1)+1,b));
}inline ll Sum(int k,int l,int r,int a,int b)
{if(a>r || b<l)return 0;if(a>=l && b<=r)return t[k].sum;Pushdown(k);return Sum(k<<1,l,r,a,(a+b)>>1)+Sum(k<<1|1,l,r,((a+b)>>1)+1,b);
}int main()
{
//    freopen("input.txt","r",stdin);int T;scanf("%d",&T);while(T--){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",&val[i]);int sz=1;while(sz<n)sz<<=1;Build(1,1,sz);while(m--){int op,x,y,w;scanf("%d%d%d",&op,&x,&y);if(op==0){scanf("%d",&w);Modify(1,x,y,1,sz,w);}if(op==1)printf("%d\n",Max(1,x,y,1,sz));if(op==2)printf("%lld\n",Sum(1,x,y,1,sz));}for(int i=1;i<=n;i++)val[i]=0;for(int i=1;i<sz+n;i++)t[i]=Node();}return 0;
}
View Code

 

 

一道更加综合的题:BZOJ 4695 (最假女选手)

这道题目就是上一题的加强版了,不仅有区间取$min$,还有区间加,区间取$max$

所以我们不仅要保存区间和、最大值、次大值、最大值出现次数,还要保存最小值、次小值、最小值出现次数、区间加的懒标记(貌似最后一个可以不用?有时间研究一下...)

虽然大家保存的节点结构都差不多,但后面的标记传递还是有点差距的(我的写法虽然常数巨大,但是竟然能省略很多细节)

首先考虑区间加(操作$1$)

跟普通的线段树区间加是一样的操作:先定位,然后给$tag$加上这个数,并改变$sum$;每次将要访问下层节点的时候,将$tag$向下传递

但是这道题中tag的存在感比普通的区间加中的要强很多,因为对于每个线段树节点,最值、次最值都是相对值,真实的值需要加上这个节点的$tag$(这个在操作$2$、$3$出现)

然后是区间取$min$(操作$3$) <-跟上一题一样,讲起来方便

先递归到严格区间内,然后分三步操作(比较的时候记得加上$tag$)

但是这里存在比上一题麻烦的地方:修改区间最大、次大值的同时,我们还有可能改变区间的最小、次小值

别的大佬们在这里就直接讨论了,但我写的时候没有想那么多(怀疑常数出现在这里)

我们先考虑一下,在什么时候才会从操作$3$的递归进入对单点的修改

这下,最小、次小值改变的条件就清晰一些了

  • 如果存在次小值,且最大值等于次小值,次小值改变,最小值不变
  • 如果不存在次小值(即区间内全是最大值,此时最小值也是最大值),那么最小值改变,次小值仍不存在
  • 否则(即次小值存在且小于最大值),最小值、次小值都不改变

区间取$max$(操作$2$)是这个的镜像操作,别写岔就行了

要记得将$tag$、小值、大值向下传递(与前一题差不多),并在递归后更新节点(我写的巨丑,也是大常数的来源...有必要再学习一下别人的代码)

这题跑了$46000+MS$,快反向登顶了,哎...

#include <cstdio>
#include <cstring>
#include <cmath>
#include <ctime>
#include <algorithm>
using namespace std;typedef long long ll;
const int MAX=500005;
const int INF=1<<30;struct Node
{ll sum;int tag,len;int fst[2],sec[2],cnt[2];Node(){fst[0]=sec[0]=-INF;fst[1]=sec[1]=INF;cnt[0]=cnt[1]=sum=tag=len=0;}Node(int x){fst[0]=fst[1]=x;sec[0]=-INF,sec[1]=INF;cnt[0]=cnt[1]=len=1;sum=x,tag=0;}
};int n,m,sz=1;
int val[MAX<<1];
Node t[MAX<<2];inline void Update(int k)
{Node l=t[k<<1],r=t[k<<1|1];t[k].sum=l.sum+r.sum;for(int i=0;i<2;i++){     l.fst[i]=l.fst[i]+l.tag-t[k].tag;l.sec[i]=l.sec[i]+l.tag-t[k].tag;r.fst[i]=r.fst[i]+r.tag-t[k].tag;r.sec[i]=r.sec[i]+r.tag-t[k].tag;if(l.fst[i]==r.fst[i]){t[k].fst[i]=l.fst[i];if(i==0)t[k].sec[i]=max(l.sec[i],r.sec[i]);elset[k].sec[i]=min(l.sec[i],r.sec[i]);t[k].cnt[i]=l.cnt[i]+r.cnt[i];}else{if(i==0 && l.fst[i]<r.fst[i])swap(l,r);if(i==1 && l.fst[i]>r.fst[i])swap(l,r);t[k].fst[i]=l.fst[i];if(i==0)t[k].sec[i]=max(l.sec[i],r.fst[i]);elset[k].sec[i]=min(l.sec[i],r.fst[i]);t[k].cnt[i]=l.cnt[i];}}
}inline void Build(int k,int r,int a,int b)
{if(a>r)return;if(a==b){t[k]=Node(val[a]);return;}Build(k<<1,r,a,(a+b)>>1);Build(k<<1|1,r,((a+b)>>1)+1,b);Update(k);t[k].len=t[k<<1].len+t[k<<1|1].len;
}inline void DecMax(int k,ll w)
{w-=t[k].tag;if(t[k].fst[0]<=w)return;t[k].sum-=(ll)t[k].cnt[0]*(t[k].fst[0]-w);if(t[k].fst[0]==t[k].sec[1])t[k].sec[1]=w;if(t[k].fst[0]==t[k].fst[1])t[k].fst[1]=w;t[k].fst[0]=w;
}inline void DecMin(int k,ll w)
{w-=t[k].tag;if(t[k].fst[1]>=w)return;t[k].sum-=(ll)t[k].cnt[1]*(t[k].fst[1]-w);if(t[k].fst[1]==t[k].sec[0])t[k].sec[0]=w;if(t[k].fst[1]==t[k].fst[0])t[k].fst[0]=w;t[k].fst[1]=w;
}inline void DecTag(int k)
{if(t[k].tag==0)return;for(int i=0;i<2;i++){t[k].fst[i]+=t[k].tag;t[k].sec[i]+=t[k].tag;}if(k<sz){t[k<<1].tag+=t[k].tag;t[k<<1].sum+=(ll)t[k].tag*t[k<<1].len;t[k<<1|1].tag+=t[k].tag;t[k<<1|1].sum+=(ll)t[k].tag*t[k<<1|1].len;}t[k].tag=0;
}inline void Down(int k)
{DecTag(k);DecMax(k<<1,t[k].fst[0]);DecMin(k<<1,t[k].fst[1]);DecMax(k<<1|1,t[k].fst[0]);DecMin(k<<1|1,t[k].fst[1]);
}inline void Add(int k,int l,int r,int a,int b,int w)
{if(a>r || b<l)return;if(a>=l && b<=r){t[k].tag+=w;t[k].sum+=(ll)t[k].len*w;return;}Down(k);Add(k<<1,l,r,a,(a+b)>>1,w);Add(k<<1|1,l,r,((a+b)>>1)+1,b,w);Update(k);
}inline void ModifyMax(int k,int l,int r,int a,int b,int w)
{if(a>r || b<l)return;if(a>=l && b<=r){if(t[k].fst[1]+t[k].tag>=w)return;if(t[k].sec[1]+t[k].tag>w){DecMin(k,w);return;}}Down(k);ModifyMax(k<<1,l,r,a,(a+b)>>1,w);ModifyMax(k<<1|1,l,r,((a+b)>>1)+1,b,w);Update(k);
}inline void ModifyMin(int k,int l,int r,int a,int b,int w)
{if(a>r || b<l)return;if(a>=l && b<=r){if(t[k].fst[0]+t[k].tag<=w)return;if(t[k].sec[0]+t[k].tag<w){DecMax(k,w);return;}}Down(k);ModifyMin(k<<1,l,r,a,(a+b)>>1,w);ModifyMin(k<<1|1,l,r,((a+b)>>1)+1,b,w);Update(k);
}inline ll QuerySum(int k,int l,int r,int a,int b)
{if(a>r || b<l)return 0LL;if(a>=l && b<=r)return t[k].sum;Down(k);return QuerySum(k<<1,l,r,a,(a+b)>>1)+QuerySum(k<<1|1,l,r,((a+b)>>1)+1,b);
}inline int QueryMax(int k,int l,int r,int a,int b)
{if(a>r || b<l)return -INF;if(a>=l && b<=r)return t[k].fst[0]+t[k].tag;Down(k);return max(QueryMax(k<<1,l,r,a,(a+b)>>1),QueryMax(k<<1|1,l,r,((a+b)>>1)+1,b));
}inline int QueryMin(int k,int l,int r,int a,int b)
{if(a>r || b<l)return INF;if(a>=l && b<=r)return t[k].fst[1]+t[k].tag;Down(k);return min(QueryMin(k<<1,l,r,a,(a+b)>>1),QueryMin(k<<1|1,l,r,((a+b)>>1)+1,b));
}int main()
{
//    freopen("input.txt","r",stdin);
//    freopen("my.txt","w",stdout); scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&val[i]);while(sz<n)sz<<=1;Build(1,n,1,sz);scanf("%d",&m);while(m--){int op,x,y,w;scanf("%d%d%d",&op,&x,&y);if(op<4)scanf("%d",&w);if(op==1)Add(1,x,y,1,sz,w);if(op==2)ModifyMax(1,x,y,1,sz,w);if(op==3)ModifyMin(1,x,y,1,sz,w);if(op==4)printf("%lld\n",QuerySum(1,x,y,1,sz));if(op==5)printf("%d\n",QueryMax(1,x,y,1,sz));if(op==6)printf("%d\n",QueryMin(1,x,y,1,sz));}return 0;
}
View Code

 

线段树合并

这个一开始学起来真的有点困难...稍微有点抽象

在我的感觉中,是处理有关元素的有序性的问题的

例如将两个区间$[1,8]$的统计元素出现次数的线段树合并

我们先想想如何实现

为了防止空间爆炸,我们写的线段树必然是动态开点的,那么便有一大优势:可以类似可持久化线段树一样,通过将新节点的儿子指向已经存在的某节点来重复利用

在这个基础上考虑将两棵线段树对于一个区间的两个节点$x$、$y$合并成一个新节点

  1. 若$x$、$y$都为空,则合并后也为空
  2. 若$x$、$y$其中一个为空,那么可以直接指向不为空的节点(因为与空树合并,原本的结构不变)
  3. 若$x$、$y$都不为空,那么分为左、右儿子继续向下递归,最后在将左右的统计值相加

(其实还是有很多细节的,最好学习写法比较好的代码,比如后面引用到的)

 

这样下来,复杂度是怎么样的呢?

首先一般情况,如果一颗数值区间为$[1,n]$的线段树是由$n$条链依次合并成的,那么就跟可持久化线段树一样,是$O(NlogN)$的时间复杂度

但是总有特例吧,比如下图,两棵线段树在叶节点互补(意会一下)

这样的一次合并是$O(N)$的

但是不要着急,如果想构成这样的情形,首先这两个树应当分别构造,且如果想让单次合并的消耗尽量大,在叶节点处应当同样互补

这样总的算下来,时间复杂度是$O(N+2\times \frac{N}{2}+4\times \frac{N}{4}+...)=O(NlogN)$,可以放心食用

由于合并大多伴随着开点,所以空间复杂度也是$O(NlogN)$,应当注意不要MLE

 

说了这么多,具体代码怎么实现比较好,又有什么用呢?

且看这道题目:BZOJ 2212 ($POI2011$,$Tree Rotations$)

我完全看的是这篇题解:胡小兔:神奇的操作——线段树合并(例题: BZOJ2212)

具体题目分析和代码实现都无可挑剔,我就不做无用功了

 

但是仅仅做出这一道题离熟练掌握还差距很大

再来一道题目:BZOJ 4552 ($TJoi2016$ & $HEoi2016$,排序)

准备学习一下这份代码:fjzzq2002:线段树合并与分裂

这道题目更加综合一些,不仅要求线段树的合并,还有分裂

其实在掌握合并的基础上,分裂也是容易处理的

加入我们想在以$root$为根的线段树上将第$p$个元素(包含第$p$个)分裂成一颗新树,就可以类似主席树上找第$k$大的递归处理

对于当前节点$x$($t_x$表示,节点$x$覆盖的区间共有多少元素)

  • 若$t_{l_i}\le p$,那么第$p$个元素一定在$x$的左儿子中,将$r_x$直接割给新树
  • 若$t_{l_i}>p$,那么第$p$个元素一定在$x$的右儿子中
  • 若已经到达底端,则此时$x$就是以$root$为根的线段树中第$p$个元素,将$x$割给新树(在这里,我的实现是将$t_x$清零,将新树中对于节点$t_{cur}$赋成$1$)

然后可以删去一些分裂中被割走的节点:如果对于当前节点$x$,$t_{l_x}==0$,即左子树为空,那么令$l_x=0$以免之后的合并访问到这个没必要经过的节点;对于$r_x$同理

 

在这题中,除去了线段树的合并与分裂(总体应该是$O(NlogN)$,但是具体不会证),我们还需要一种数据结构告诉我们应该合并哪些线段树

一开始的想法是用一颗区间赋值线段树给合并后的每一段打上懒标记,但是如果想要从一段合并区间到另一段,感觉上需要在树上累加,应该是$O(logN)$的跳转,细节还很多

zzq给出的思路是用set,排序的依据是区间的左端点

这样一来,我们对每次排序的$[op,x,y]$操作,能够直接二分$O(logN)$的找出左右端点所在的合并区间,然后再通过$iterator$遍历将线段树合并

(不过我对set十分陌生,一开始将s.lower_bound(x)写成lower_bound(s.begin(),s.end(),x),应该是不能这样用的)

他是set<int>,我写的是set<pair<int,int> >,细节还非常的繁琐...应当学习一下他的处理方法

除去set的部分,我的代码还是能看的

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <set>
using namespace std;typedef pair<int,int> pii;
const int MAX=100005;
const int LOG=100;int tot;
int l[MAX*LOG],r[MAX*LOG],t[MAX*LOG],rev[MAX*LOG];inline int Build(int x,int a,int b)
{int cur=++tot;t[cur]=1;if(a==b)return cur;int mid=(a+b)>>1;if(x<=mid)l[cur]=Build(x,a,mid);elser[cur]=Build(x,mid+1,b);return cur;
}inline int Merge(int x,int y,int a,int b)
{if(!x || !y)return x+y;int cur=++tot,mid=(a+b)>>1;l[cur]=Merge(l[x],l[y],a,mid);r[cur]=Merge(r[x],r[y],mid+1,b);t[cur]=t[l[cur]]+t[r[cur]];return cur;
}inline void Update(int x)
{if(!t[l[x]])l[x]=0;if(!t[r[x]])r[x]=0;
}inline int Split(int x,int a,int b,int p)
{int cur=++tot,mid=(a+b)>>1;if(a==b){t[cur]=t[x];t[x]=0;return cur;}if(t[l[x]]>=p){l[cur]=Split(l[x],a,mid,p);r[cur]=r[x];r[x]=0;}elser[cur]=Split(r[x],mid+1,b,p-t[l[x]]);Update(x);Update(cur);t[x]=t[l[x]]+t[r[x]];t[cur]=t[l[cur]]+t[r[cur]];return cur;
}inline int Locate(int x,int a,int b,int p)
{if(a==b)return a;int mid=(a+b)>>1;if(t[l[x]]>=p)return Locate(l[x],a,mid,p);elsereturn Locate(r[x],mid+1,b,p-t[l[x]]);
}int n,m,sz=1;
int val[MAX];
set<pii> s;int main()
{
//    freopen("input.txt","r",stdin);
//    freopen("output.txt","w",stdout);scanf("%d%d",&n,&m);while(sz<n)sz<<=1;for(int i=1;i<=n;i++){scanf("%d",&val[i]);s.insert(pii(i,Build(val[i],1,sz)));}for(int i=1;i<=m;i++){int op,x,y;scanf("%d%d%d",&op,&x,&y);set<pii>::iterator beg=s.lower_bound(pii(x,0));if((*beg).first>x || beg==s.end())beg--;set<pii>::iterator end=s.lower_bound(pii(y,0));if((*end).first>y || end==s.end())end--;int idx1=(*beg).second,left1=(*beg).first,idx2=(*end).second,left2=(*end).first;if(idx1==idx2){s.erase(beg);int p1=x-left1+1,p2=y-left1+2;int rp1=t[idx1]-(x-left1)+1,rp2=t[idx1]-(y-left1);int left,right,mid;if(!rev[idx1]){left=idx1,mid=Split(idx1,1,sz,p1),right=Split(mid,1,sz,p2-p1+1);if(t[left])s.insert(pii(left1,left));if(t[right])s.insert(pii(y+1,right));s.insert(pii(x,mid));}else{left=idx1,mid=Split(idx1,1,sz,rp2),right=Split(mid,1,sz,rp1-rp2+1);if(t[left])s.insert(pii(y+1,left));if(t[right])s.insert(pii(left1,right));rev[left]=rev[right]=1;s.insert(pii(x,mid));}rev[mid]=op;continue;}int num1=left1+t[idx1]-x,num2=y-left2+1;int p1=t[idx1]-num1+1,p2=num2+1;int rp1=num1+1,rp2=t[idx2]-num2+1;int begn,endn,rem1,rem2,newn;if(!rev[idx1])begn=Split(idx1,1,sz,p1),rem1=idx1;elsebegn=idx1,rem1=Split(idx1,1,sz,rp1),rev[rem1]=1;//
        if(!rev[idx2])endn=idx2,rem2=Split(idx2,1,sz,p2);elseendn=Split(idx2,1,sz,rp2),rem2=idx2,rev[rem2]=1;//
        newn=Merge(begn,endn,1,sz);set<pii>::iterator it=beg;it++;while(it!=end)newn=Merge(newn,(*it).second,1,sz),it++;s.erase(beg,end);s.erase(end);if(t[rem1])s.insert(pii(left1,rem1));if(t[rem2])s.insert(pii(y+1,rem2));s.insert(pii(x,newn));rev[newn]=op;}int q;scanf("%d",&q);set<pii>::iterator it=s.lower_bound(pii(q,0));if((*it).first>q || it==s.end())//
        it--;int x=(*it).second,left=(*it).first;int p=(rev[x]?t[x]-(q-left):q-left+1);printf("%d\n",Locate(x,1,sz,p));return 0;
}
View Code

 

这题还有一种玩法是$O(N(logN)^2)$的二分+线段树区间修改,网上大部分都是这样的题解,也是一种有趣的思路吧

#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;const int MAX=100005;int n,m,q;
int val[MAX],op[MAX],ql[MAX],qr[MAX];
int c[MAX];int sz=1;
int t[MAX<<2],len[MAX<<2],tag[MAX<<2];inline void Build(int k,int l,int r)
{if(l==r){if(l<=n){t[k]=c[l];len[k]=1;}return;}int mid=(l+r)>>1;Build(k<<1,l,mid);Build(k<<1|1,mid+1,r);t[k]=t[k<<1]+t[k<<1|1];len[k]=len[k<<1]+len[k<<1|1];
}inline void Update(int x)
{if(!tag[x])return;t[x<<1]=(tag[x]>0?len[x<<1]:0);tag[x<<1]=tag[x];t[x<<1|1]=(tag[x]>0?len[x<<1|1]:0);tag[x<<1|1]=tag[x];tag[x]=0;
}inline int Query(int k,int l,int r,int a,int b)
{if(a>r || b<l)return 0;if(a>=l && b<=r)return t[k];Update(k);int mid=(a+b)>>1;return Query(k<<1,l,r,a,mid)+Query(k<<1|1,l,r,mid+1,b);
}inline void Add(int k,int l,int r,int a,int b,int w)
{if(a>r || b<l)return;if(a>=l && b<=r){tag[k]=w;t[k]=(w>0?len[k]:0);return;}Update(k);int mid=(a+b)>>1;Add(k<<1,l,r,a,mid,w);Add(k<<1|1,l,r,mid+1,b,w);t[k]=t[k<<1]+t[k<<1|1];//
}int Check(int x)
{memset(tag,0,sizeof(tag));for(int i=1;i<=n;i++)c[i]=(val[i]<=x);Build(1,1,sz);for(int i=1;i<=m;i++){int x=ql[i],y=qr[i],num=Query(1,x,y,1,sz);if(op[i]==0){Add(1,x,x+num-1,1,sz,1);Add(1,x+num,y,1,sz,-1);}else{Add(1,x,y-num,1,sz,-1);Add(1,y-num+1,y,1,sz,1);}}return Query(1,q,q,1,sz);
}int main()
{
//    freopen("input.txt","r",stdin);
//    freopen("output.txt","w",stdout);scanf("%d%d",&n,&m);while(sz<n)sz<<=1;for(int i=1;i<=n;i++)scanf("%d",&val[i]);for(int i=1;i<=m;i++)scanf("%d%d%d",&op[i],&ql[i],&qr[i]);scanf("%d",&q);int l=1,r=n,mid;while(l<r){mid=(l+r)>>1;if(Check(mid))r=mid;elsel=mid+1;}if(!Check(l))l++;printf("%d\n",l);return 0;
}
View Code

 

(待续,慢慢补题)

转载于:https://www.cnblogs.com/LiuRunky/p/Segment_Tree.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务实践沙龙-上海站

微服务的概念最早由Martin Fowler与James Lewis于2014年共同提出&#xff0c;核心思想是围绕业务能力组织服务&#xff0c;各个微服务可被独立部署&#xff0c;服务间是松耦合的关系&#xff0c;以及数据和治理的去中心化管理。微服务能够帮助企业应对业务复杂、频繁更新以及团…

(四)RabbitMQ消息队列-服务详细配置与日常监控管理

&#xff08;四&#xff09;RabbitMQ消息队列-服务详细配置与日常监控管理 原文:&#xff08;四&#xff09;RabbitMQ消息队列-服务详细配置与日常监控管理RabbitMQ服务管理 启动服务&#xff1a;rabbitmq-server -detached【 /usr/local/rabbitmq/sbin/rabbitmq-server -deta…

前端开发工程化探讨--基础篇(长文)

转载自UC资深前端工程师张云龙的github 喂喂喂&#xff0c;那个切图的&#xff0c;把页面写好就发给研发工程师套模板吧。 你好&#xff0c;切图仔。 不知道你的团队如何定义前端开发&#xff0c;据我所知&#xff0c;时至今日仍然有很多团队会把前端开发归类为产品或者设计岗…

Python读取Json字典写入Excel表格的方法

需求&#xff1a; 因需要将一json文件中大量的信息填入一固定格式的Excel表格&#xff0c;单纯的复制粘贴肯定也能完成&#xff0c;但是想偷懒一下&#xff0c;于是借助Python解决问题。 环境&#xff1a; Windows7 Python2.7 Xlwt 具体分析&#xff1a; 原始文件为json列表&am…

Spring-BeanFactory源码分析

正式进入Spring 源码分析这个模块了&#xff0c;对于spring这个庞大的工程&#xff0c;如果要一点点的完全分析是非常困难的&#xff0c;对于应用型框架&#xff0c;我还是偏向于掌握思想或者设计&#xff0c;而不是记住代码&#xff0c;对于初次看spring源码&#xff0c;相信大…

我所知道的HTTP和HTTPS

摘要&#xff1a;相比之前的传输协议&#xff0c;HTTP/2在底层方面做了很多优化。有安全、省时、简化开发、更好的适应复杂页面、提供缓存利用率等优势&#xff0c;阿里云早在去年发布的CDN6.0服务就已正式支持HTTP/2&#xff0c;访问速度最高可提升68%。 写在前面 超文本传输…

Jenkins配置与使用

Jenkins是一个开源软件项目&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。Jenkins是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;功能包括&#xff1a;1、持续的软件版本发布/测试项目。2、监控外部调用…

fastDFS使用

fastDFS : 分布式文件系统C语言开发,fastDFS为互联网量身定制,考虑到了冗余备份,负载均衡,线性扩容...很容易搭建集群文件存储系统.存储在fastDFS图片:相当于存储在本地磁盘一样访问图片:相当于访问本地磁盘存储结构:组名/虚拟磁盘路径/动态生成文件名.扩展名192.168.100.20/gr…

本地环境用eclipse搭建spring源码环境

对于JAVA和.NET开发人员来讲Spring框架并不陌生&#xff0c;对于想进行spring源码学习的同学来讲&#xff0c;在本地下载和构建spring项目很有必要。以下简要说明下Spring源码的下载和在eclipse下的构建方式。 工具/原料 JDK Eclipse 我们需要从源码库下载Spring的源码文件到本…

SpringToolsSuite (STS)或Eclipse安装gradle

对于新手刚进入职场&#xff0c;不知怎么在Spring Tools Suite (STS)或Eclipse上安装gradle&#xff0c;因为该项目自动化构建开源工具在一些企业中是要用的。本经验介绍如何安装。 工具/原料 Spring Tools Suite (STS)或Eclipse开发工具 gradle-5.0-all.zip压缩包 下载Gradle…

spring-beans模块分析

描述&#xff1a;spring-beans负责实现Spring框架的IOC模块 UML结构图如下&#xff1a; AbstractBeanFactory:BeanFactory接口的抽象实现类&#xff0c;提供了ConfigurableBeanFactory 完整SPI。 通过DefaultSingletonBeanRegistry实现了单例缓存(singleton cache). 实现了通过…

Vue学习【第六篇】:Vue-cli脚手架(框架)与实战案例

环境搭建 安装node 官网下载安装包&#xff0c;傻瓜式安装&#xff1a;https://nodejs.org/zh-cn/ 安装cnpm npm install -g cnpm --registryhttps://registry.npm.taobao.org 安装脚手架 cnpm install -g vue/cli 清空缓存处理 npm cache clean --force 项目的创建 创建项目 v…

递归函数实现二分查找法

最初版本&#xff1a; 改进版&#xff1a; 最终版本&#xff1a; 递归实现阶乘&#xff1a; 转载于:https://www.cnblogs.com/www-qcdwx-com/p/10399288.html

图解LinkedHashMap原理

1 前言 LinkedHashMap继承于HashMap&#xff0c;如果对HashMap原理还不清楚的同学&#xff0c;请先看上一篇&#xff1a;图解HashMap原理 2 LinkedHashMap使用与实现 先来一张LinkedHashMap的结构图&#xff0c;不要虚&#xff0c;看完文章再来看这个图&#xff0c;就秒懂了…

2019 GUDT RC 2 Problem C(题解)

原题 题目大意 这道题的背景是农夫和牛爬山,给出山的高度L,农夫会从山底以rF的速度爬山,中途不会休息,牛会从山底以rB的速度爬山,可以在休息站休息并吃草,在第i个休息站休息ti时间,牛可以吃t*ci的草,第i个休息站的高度为xi.农夫和牛同时出发,要求牛在不被农夫追上的同时吃最多的…

Express + Element-ui 实现图片/文件上传

使用第三方插件 formidable 处理表单数据/文件 Express 4 以前&#xff0c;我们通常使用 req.files 来对请求中的文件进行处理&#xff0c;但在 Express 4 中这种用法已经被抛弃&#xff0c;默认情况下 req.files 在 req 对象上不再可用。官方推荐我们使用第三方中间件。 在这里…

10-04 矩形覆盖(斐波那契数列的应用)

题目描述&#xff1a; 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形&#xff0c;总共有多少种方法&#xff1f; 解题思路与代码&#xff1a; 1&#xff09; 排列组合&#xff1a; class Solution { public:int rectC…

Spring 源码分析 spring-core

先来看下 spring-core 的包结构 总共有6个模块&#xff0c;分别是 asm、cglib、core、lang、objenesis、util asm包&#xff1a; 用来操作字节码&#xff0c;动态生成类或者增强既有类的功能。主要包含以下这些类。详细功能。 https://www.ibm.com/developerworks/cn/java/j…

大数据项目中的QA需要迎接新的挑战

大数据项目中的QA需要迎接新的挑战根据IDC全球半年度大数据和分析支出指南的最新预测&#xff0c;到2022年全球大数据和业务分析解决方案的收入将达到2600亿美元。在大数据和业务分析解决方案上投资增长最快的行业包括银行&#xff08;复合年增长率13.3%&#xff09;、医疗、保…