一些奇妙的線段樹操作

學過數據結構和會做題完全是兩個概念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.pswp.cn/news/249659.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/249659.shtml
英文地址,請注明出處:http://en.pswp.cn/news/249659.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

微服務實踐沙龍-上海站

微服務的概念最早由Martin Fowler與James Lewis于2014年共同提出&#xff0c;核心思想是圍繞業務能力組織服務&#xff0c;各個微服務可被獨立部署&#xff0c;服務間是松耦合的關系&#xff0c;以及數據和治理的去中心化管理。微服務能夠幫助企業應對業務復雜、頻繁更新以及團…

Spring的refresh()方法調用過程

Spring的refresh()方法調用過程 refresh()是Spring中比較核心的方法&#xff0c;Spring所有的初始化都在這個方法中完成 具體代碼如下 public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this co…

Web數據存儲之localStorage和sessionStorage

Web數據存儲之localStorage和sessionStorage 學習前端以來&#xff0c;自己了解有localStorage和sessionStorage的相關存儲的知識&#xff0c;也有實踐過&#xff0c;但是之前只限于能用的基礎上&#xff0c;但最近看了一本書&#xff0c;深入了解了localStorage和sessionStor…

(四)RabbitMQ消息隊列-服務詳細配置與日常監控管理

&#xff08;四&#xff09;RabbitMQ消息隊列-服務詳細配置與日常監控管理 原文:&#xff08;四&#xff09;RabbitMQ消息隊列-服務詳細配置與日常監控管理RabbitMQ服務管理 啟動服務&#xff1a;rabbitmq-server -detached【 /usr/local/rabbitmq/sbin/rabbitmq-server -deta…

oracle中delete、truncate、drop的區別 (轉載)

一、delete 1、delete是DML&#xff0c;執行delete操作時&#xff0c;每次從表中刪除一行&#xff0c;并且同時將該行的的刪除操作記錄在redo和undo表空間中以便進行回滾&#xff08;rollback&#xff09;和重做操作&#xff0c;但要注意表空間要足夠大&#xff0c;需要手動提交…

前端開發工程化探討--基礎篇(長文)

轉載自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;相信大…

Linux查看修改時間、時區

同步網絡時間 yum install ntpntpdate time.nist.gov timedatectl set-timezone Asia/Shanghai如果上面time.nist.gov服務器同步不了&#xff0c;可以換下面幾個時間服務器試試&#xff1a;time.nist.govtime.nuri.net0.asia.pool.ntp.org1.asia.pool.ntp.org2.asia.pool.ntp.o…

我所知道的HTTP和HTTPS

摘要&#xff1a;相比之前的傳輸協議&#xff0c;HTTP/2在底層方面做了很多優化。有安全、省時、簡化開發、更好的適應復雜頁面、提供緩存利用率等優勢&#xff0c;阿里云早在去年發布的CDN6.0服務就已正式支持HTTP/2&#xff0c;訪問速度最高可提升68%。 寫在前面 超文本傳輸…

sql server常用性能計數器

https://blog.csdn.net/kk185800961/article/details/52462913?utm_sourceblogxgwz5 https://blog.csdn.net/kk185800961/article/details/27657239 以下部分轉自&#xff1a;http://www.cnblogs.com/zhijianliutang/p/4174697.html 常規計數器 收集操作系統服務器的服務器性能…

Python中正反斜杠('/'和'\')的意義

剛剛在學習些測試報告的時候&#xff0c;出現一個路徑的問題&#xff0c;找了很久的原因&#xff0c;竟然是少了一個反斜杠引起的&#xff0c;在此順便記錄一下正反斜杠的作用。 在Python中&#xff0c;記錄路徑時有以下幾種寫法&#xff0c;如&#xff1a;&#xff08;大家都知…

什么是IOC容器

1.IOC不是一種技術&#xff0c;只是一種思想&#xff0c;一個重要的面向對象編程的法則&#xff0c;它能指導我們如何設計出松耦合&#xff0c;更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象&#xff0c;從而導致類與類之間高耦合&#xff0c;難于測試&#x…

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…

[NOI2007]貨幣兌換

題目 先來畫一畫柿子 設\(dp_i\)表示你第\(i\)天之后最多剩下多少錢 考慮一下對于\(i\)的轉移&#xff0c;我們肯定要在之前枚舉一天\(j\)這一天把所有的東西買進來&#xff0c;之后在\(i\)天賣掉 設那天買進\(A\)的量為\(d_a\)&#xff0c;買進\(B\)的量為\(d_b\) 我們可以得到…

spring-beans模塊分析

描述&#xff1a;spring-beans負責實現Spring框架的IOC模塊 UML結構圖如下&#xff1a; AbstractBeanFactory:BeanFactory接口的抽象實現類&#xff0c;提供了ConfigurableBeanFactory 完整SPI。 通過DefaultSingletonBeanRegistry實現了單例緩存(singleton cache). 實現了通過…

spark-streaming first insight

一、 Spark Streaming 構建在Spark core API之上&#xff0c;具備可伸縮&#xff0c;高吞吐&#xff0c;可容錯的流處理模塊。 1&#xff09;支持多種數據源&#xff0c;如Kafka&#xff0c;Flume&#xff0c;Socket&#xff0c;文件等&#xff1b; Basic sources: Sources dir…