Hello,我回來更新線段樹系列了。c++
因爲目前鴿掉的文章有點多...因此只能慢慢填坑了。數組
最近聯賽複習的時候寫了幾道以爲不錯的線段樹題,正好能夠回來填個坑。數據結構
首先咱們來看看這道題:LuoguP4145
ui
這道題須要咱們寫一個數據結構,支持下面兩種操做:
1. 區間開平方 2. 區間求和spa
能夠區間開平方的數據結構其實沒有(別跟我說Chtholly Tree,這題沒有區間賦值用不了...)。code
可是咱們注意到題目中說的:向下取整。並且咱們還能夠發現,數列中的數大於0,小於$10^{12}$。blog
咱們知道sqrt(1)=1。也就是說當咱們開方開到1的時候,咱們就不用對那段區間進行修改了。排序
經計算,咱們僅須要最多6次開方操做,就能把數列中的全部數開方到1,那咱們直接單點修改就行了。get
維護區間和的同時維護一下區間最值,只有區間最值>1咱們才進入修改,不然咱們就跳過它。it
給出代碼(我寫的動態開點線段樹...最近比較喜歡寫這個,固然你用普通線段樹也是能夠秒這道題的)
#include<bits/stdc++.h> using namespace std; #define int long long inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } const int N=1e5+10; struct Segment_Tree{ int l,r; int sum,mx; #define lc(x) tree[x].l #define rc(x) tree[x].r #define sum(x) tree[x].sum #define mx(x) tree[x].mx }tree[N<<2]; int ncnt,rt; int n,a[N],m; inline int insert(){ ncnt++; lc(ncnt)=rc(ncnt)=sum(ncnt)=0; return ncnt; } void pushup(int o){ sum(o)=sum(lc(o))+sum(rc(o)); mx(o)=max(mx(lc(o)),mx(rc(o))); } void build(int&o,int l,int r){ o=insert(); if(l==r){ sum(o)=mx(o)=a[l];return; } int mid=(l+r)>>1; build(lc(o),l,mid);build(rc(o),mid+1,r); pushup(o); } void update(int&o,int l,int r,int L,int R){ if(!o)o=insert(); if(l==r){ sum(o)=sqrt(sum(o));mx(o)=sqrt(mx(o)); return; } int mid=(l+r)>>1; if(L<=mid && mx(lc(o))>1)update(lc(o),l,mid,L,R); if(R>mid && mx(rc(o))>1)update(rc(o),mid+1,r,L,R); pushup(o); } int query(int o,int l,int r,int L,int R){//當前區間,查詢區間 if(!o)return 0; if(L<=l && R>=r)return sum(o); int mid=(l+r)>>1; int val=0; if(L<=mid)val+=query(lc(o),l,mid,L,R); if(R>mid)val+=query(rc(o),mid+1,r,L,R); return val; } signed main(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); build(rt,1,n); m=read(); int k,l,r; for(int i=1;i<=m;i++){ k=read();l=read();r=read(); if(l>r)swap(l,r); if(k==0) update(rt,1,n,l,r); else printf("%lld\n",query(rt,1,n,l,r)); } return 0; }
而後咱們接着來看一下這樣一道題目:
Continuous Intervals。
題目大意:給定一個長度爲N的序列,定義連續區間[l,r]爲:序列的一段子區間,知足[l,r]中的元素從小到大排序後,任意相鄰兩項的差值不超過1。求一共有多少個連續區間。
這是一道區間計數類的問題。對於這類問題,其實我以前接觸的比較少,不太會寫。最近數數題考的又比較多,因此我以爲這是一道不錯的練習題。(同機房的dalao們都說水...
對於這類題目咱們有一個比較常規的解決方法就是:枚舉區間的一個端點,統計以該點爲答案的區間個數加入答案貢獻。
那對於這道題,咱們能夠枚舉它的右端點r。而後對於每一個枚舉的右端點r,咱們須要快速地求出有多少個左端點l知足連續區間的性質,咱們須要在O(logn)的時間解決。
咱們來分析一下連續區間的定義,這裏實際上是一個很是巧妙的轉換,我本身徹底沒有想到...
咱們發現,若是要求排好序後相鄰兩項的差值不超過1,那咱們排好序後定義就轉化爲:
$max[a_l...a_r]-min[a_l...a_r]=cnt-1$,其中cnt表示區間內不一樣數字的個數。
移項得:$max-min-cnt=-1$,咱們只須要維護區間的max-min-cnt就行了。
這個能夠用線段樹來實現:
咱們維護兩個單調棧來實現對max和min的維護,而後再用區間加減的形式更新max和min對區間內max-min-cnt的貢獻。
而後對於區間內不一樣數字個數,這是一個很經典的問題,咱們有兩種方式維護。
首先,若是數據大小比較小,咱們能夠開一個pre數組記錄它上一次出現的位置,而後一樣的使用區間加減來維護它對max-min-cnt的貢獻。這道題的數據是1e9,咱們能夠離散化或者開一個STL的map來作。
而後這道題就很簡單了,咱們從1到n枚舉右端點r,對於每個枚舉到的r,咱們更新以它爲右端點的區間[l,r]的max-min-cnt,最後再統計一下這個值=-1的區間個數。
給出代碼:
#include<bits/stdc++.h> using namespace std; char buf[5000010],*pos,*End; #define getchar gc inline char gc(){ if(pos==End){ End=(pos=buf)+fread(buf,1,5000000,stdin); if(pos==End)return EOF; }return *pos++; } inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } #define ll long long #define LOG 4 const int N=1e5+10; struct SegmentTree{ int l,r; ll val,cnt,add; #define lc(x) tree[x].l #define rc(x) tree[x].r #define val(x) tree[x].val #define cnt(x) tree[x].cnt #define add(x) tree[x].add }tree[N*LOG]; int n,ncnt,rt; int a[N]; void pushup(int o){ if(val(lc(o))==val(rc(o))){ val(o)=val(lc(o)); cnt(o)=cnt(lc(o))+cnt(rc(o)); }else if(val(lc(o))<val(rc(o))){ val(o)=val(lc(o)); cnt(o)=cnt(lc(o)); }else{ val(o)=val(rc(o)); cnt(o)=cnt(rc(o)); } } inline void pushdown(int o){ if(add(o)){ val(lc(o))+=add(o);add(lc(o))+=add(o); val(rc(o))+=add(o);add(rc(o))+=add(o); add(o)=0; } } inline int insert(){ ++ncnt; lc(ncnt)=rc(ncnt)=val(ncnt)=cnt(ncnt)=add(ncnt)=0; return ncnt; } void build(int&o,int l,int r){ o=insert(); if(l==r){ val(o)=add(o)=0;cnt(o)=1; return; } int mid=(l+r)>>1; build(lc(o),l,mid); build(rc(o),mid+1,r); pushup(o); } void update(int o,int l,int r,int L,int R,ll v){ if(!o)o=insert(); if(L<=l&&R>=r){ val(o)+=v;add(o)+=v; return; } int mid=(l+r)>>1; pushdown(o); if(L<=mid)update(lc(o),l,mid,L,R,v); if(R>mid)update(rc(o),mid+1,r,L,R,v); pushup(o); } int main(){ int n=read(); for(int i=1;i<=n;i++)a[i]=read(); build(rt,1,n); vector<pair<int,int> > mii(n+7),mxx(n+7);//開兩個單調棧維護max和min int tp1=0;int tp2=0; map<int,int> pre; ll ans=0;int cur; for(int i=1;i<=n;i++){ cur=i; while(tp1>0&&a[i]<mii[tp1].first){ int pos=mii[tp1-1].second; update(rt,1,n,pos+1,cur-1,mii[tp1].first-a[i]); //更新一下max-min-cnt,因爲是找到了更小的min,因此這一段的max-min-cnt變小了這麼多 --tp1; cur=pos+1; } mii[++tp1]=make_pair(a[i],i);//放進單調棧 cur=i; while(tp2>0&&a[i]>mxx[tp2].first){ int pos=mxx[tp2-1].second; update(rt,1,n,pos+1,cur-1,a[i]-mxx[tp2].first); //找到了更大的max-min-cnt,也要讓這一段的max加上這麼多 --tp2; cur=pos+1; } mxx[++tp2]=make_pair(a[i],i);//放進單調棧 if(pre.find(a[i])!=pre.end()){//若是找到了和以前同樣的顏色不是在最後,也就是說不是拼在一塊的 int pos=pre[a[i]];//上一個的位置 update(rt,1,n,pos+1,i,-1); //這一段的數量在上面重複算了,上一個一樣顏色的位置到本身這一段的cnt都要-1 }else update(rt,1,n,1,i,-1); //若是和前面一段的顏色拼起來了,也就是說末尾的上一個顏色和本身的顏色同樣,前面的cnt就要-1 pre[a[i]]=i;//更新上一個這個顏色出現位置 if(val(rt)==-1)//若是值爲-1,就更新答案 ans+=cnt(rt); } printf("%lld ",ans); return 0; }
就先講這兩道題吧,之後再遇到了好題就繼續往裏面補充。