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; }
就先讲这两道题吧,之后再遇到了好题就继续往里面补充。