线段树
以下只是自己对于线段树的看法:
线段树大约是用来快速对区间进行加减和求和,并快速查询值的操作。
但是,使用前提是这个操作必须可以分治,即a[i,j]=check(a[i,mid],a[mid+1,j])
浅谈几个其中思想 很浅很浅的浅谈
0、递归建树中,放在递归操作之后的操作代表着让程序自底向上进行的操作;放在递归之前的代表着让程序自上向底进行的操作。
线段树操作中有push_up()和push_down()操作
1、懒人标记(lazy tag)的应用(后面具体会描述)
2、递归操作的运用
以下是具体操作:
首先
对于空间,我们需要开 ans[4*h],num[h],tag[4*h],一般对于h个节点,开(h<<2)的空间。
其次
我们需要定义如下几个辅助函数:
1、左右孩子节点
inline int lch(int p){return p<<1;} inline int rch(int p){return p<<1|1;}
这里使用位运算不是为了装x
2、自下向上递归的操作
inline void push_up(int p){ans[p]=ans[lch(p)]+ans[rch(p)];} //ans[p]=max(ans[lch(p)],ans[rch(p)]) //ans[p]=func(ans[lch[p]],ans[[rch(p)]])
即当程序自子树向上递归的时候,完成线段树区间的更新
3、递归建树
void build(int p,int l,int r){ tag[p]=0;//懒人节点初始化,后面会讲 if(l==r){ans[p]=num[l];return;} //只有叶子节点才真正存储每一个单个元素 int mid=l+(r-l)/2; //递归建树 build(lch(p),l,mid); build(rch(p),mid+1,r); push_up(p); }
4、lazy tag and update(:
update函数含义是:给定p节点(代表[l,r]区间,给定需要修改的区间[L,R],需要修改的操作k,对[l,r]这个区间进行这个操作)
思想:对于区间[L,R]进行的修改,如果大于[l,r],那么[l,r]区间对于[l,mid],[mid+1,r]的修改可以暂时不做,毕竟爷懒,用个tag[p]存下这次的操作,留到下次再进行操作。
但是,在之后对于小于[l,r]区间的操作一定要清空之前的标记过没有做的工作,于是我们有了push_down()操作,用于自p到叶子节点的更新,要放在递归前。
inline void f(int p,int l,int r,int k) { /* *这个函数代表对于代表[l,r]区间的p节点每个数字加上k */ ans[p]+=k*(r-l+1); tag[p]+=k;//这个容易忽略!这一层懒人节点清空掉不代表下一层也清掉 } inline void push_down(int p,int l,int r){ int mid=l+(r-l)/2; f(lch(p),l,mid,tag[p]); f(rch(p),mid+1,r,tag[p]); tag[p]=0;//清0 } void update(int L,int R,int l,int r,int k,int p){ if(L<=l&&r<=R){ ans[p]+=k*(r-l+1);//这里以对于区间每个数加k为例子 tag[p]+=k;//记录下未做的工作 return; } push_down(p,l,r);//清理之前没做的操作 int mid=l+(r-l)/2; if(R<=mid)update(L,R,l,mid,k,lch(p)); if(L>mid)update(L,R,mid+1,r,rch(p)); push_up(p);//当然要往回重新更新一遍啦 }
最后
查询操作
对于区间[q_l,q_r]的查询,不就是要查询[l,r]区间中包含[q_l,q_r]部分的查询嘛
int query(int q_x,int q_y,int l,int r,int p) { int res=0; if(q_x<=l&&r<=q_y)return ans[p]; int mid=(l+r)>>1; push_down(p,l,r);//清空可能出现的懒人操作 //递归查询 if(q_x<=mid)res+=query(q_x,q_y,l,mid,p*2); if(q_y>mid)res+=query(q_x,q_y,mid+1,r,p*2+1); return res; }
呼,总算写完啦。
不,最后贴上线段树模板的完整代码(与上述函数有些许不同,但是我懒的改了)
#include <iostream> #define ll long long using namespace std; const int maxn=1e6+1; unsigned ll n,m,a[maxn],ans[maxn<<2],tag[maxn<<2];//对于h个节点,一般开4h的空间 void scan(); inline void f(ll p,ll l,ll r,ll k) { tag[p]=tag[p]+k; ans[p]=ans[p]+k*(r-l+1); } inline void push_down(ll p,ll l,ll r) { ll mid=(l+r)>>1; f(p*2,l,mid,tag[p]); f(p*2+1,mid+1,r,tag[p]); tag[p]=0; } inline void push_up(ll p){ ans[p]=ans[2*p]+ans[2*p+1]; } void build(ll p,ll l,ll r){ tag[p]=0; if(l==r){ans[p]=a[l];return;} ll mid=l+((r-l)>>1); build(2*p,l,mid); build(2*p+1,mid+1,r); push_up(p); } void update(ll x,ll y,ll k,ll l,ll r,ll p){ if(x<=l&&y>=r){ ans[p]+=k*(r-l+1); tag[p]+=k; return; } ll mid=(l+r)/2; push_down(p,l,r); if(x<=mid)update(x,y,k,l,mid,p*2); if(y>mid)update(x,y,k,mid+1,r,p*2+1); push_up(p); } ll query(ll q_x,ll q_y,ll l,ll r,ll p) { ll res=0; if(q_x<=l&&r<=q_y)return ans[p]; ll mid=(l+r)>>1; push_down(p,l,r); if(q_x<=mid)res+=query(q_x,q_y,l,mid,p*2); if(q_y>mid)res+=query(q_x,q_y,mid+1,r,p*2+1); return res; } int main() { ll a1,b,c,d,e,f; scan(); build(1,1,n); while(m--){ scanf("%lld",&a1); switch(a1){ case 1: scanf("%lld%lld%lld",&b,&c,&d); update(b,c,d,1,n,1); break; case 2: scanf("%lld%lld",&e,&f); printf("%lld\n",query(e,f,1,n,1)); break; } } return 0; } void scan(){ cin>>n>>m; for(int i=1;i<=n;++i)scanf("%lld",&a[i]); }
现在,我们学会了线段树的简单操作后,让我们来看一道例题
题目描述
给定一个由小写字母组成的字符串s。 有m次操作,每次操作给定3个参数(l, r, x)。
如果x=1,将s[l]~s[r]升序排序;
如果x=0,将s[l]~s[r]降序排序。
现在需要你求出最终序列。
输入格式
第一行两个整数n, m。 第二行一个字符串s。 接下来m行每行三个由空格隔开的整数 l, r, x。
输出格式
一行一个字符串表示最终序列。
样例输入
5 2
cabcd
1 3 1
3 5 0
样例输出
abdcc
数据范围
对于 40%的数据,n,m<=1000n,m<=1000。
对于 100%的数据,n,m<=100000n,m<=100000。
具体思想:
若左孩子和右孩子字母相同,则父节点同样赋值这个字母,表示这个区间都是这个字母,个数就是r-l+1,
对于排序,其实就是把这个区间的字母按照从小到大更改一遍即可(字符串排序的一种经典思路)
最后输出,就按照字母的个数依次输出即可。
1 #include<iostream> 2 #define ll long long 3 using namespace std; 4 const int maxn=1e5+7; 5 int tree[maxn<<2]{0}; 6 int cnt[27]{0};//记录对应区间的字母个数 7 char str[maxn]; 8 int m,n,op,L,R; 9 inline ll lch(ll p){return p<<1;} 10 inline ll rch(ll p){return p<<1|1;} 11 void push_up(ll p){ 12 if(tree[lch(p)]==tree[rch(p)]) 13 tree[p]=tree[lch(p)]; 14 else 15 tree[p]=0; 16 } 17 void build(ll p,ll l,ll r){ 18 if(l==r){ 19 tree[p]=str[l]-'a'+1; 20 return; 21 } 22 ll mid=l+((r-l)>>1); 23 build(lch(p),l,mid); 24 build(rch(p),mid+1,r); 25 push_up(p); 26 } 27 void push_down(ll p){ 28 if(tree[p]){ 29 tree[lch(p)]=tree[rch(p)]=tree[p]; 30 tree[p]=0; 31 } 32 } 33 void modify(ll l,ll r,ll L,ll R,ll p,int x){ 34 if(L<=l&&r<=R){ 35 tree[p]=x; 36 return; 37 } 38 push_down(p); 39 ll mid=l+(r-l)/2; 40 if(L<=mid)modify(l,mid,L,R,lch(p),x); 41 if(R>mid)modify(mid+1,r,L,R,rch(p),x); 42 push_up(p); 43 } 44 void get_cnt(ll p,ll l,ll r,ll L,ll R){ 45 if(L<=l&&r<=R&&tree[p]){ 46 cnt[tree[p]]+=(r-l)+1; 47 return; 48 } 49 push_down(p); 50 ll mid=l+(r-l)/2; 51 if(L<=mid)get_cnt(lch(p),l,mid,L,R); 52 if(R>mid)get_cnt(rch(p),mid+1,r,L,R); 53 } 54 void print(ll p,ll l,ll r){ 55 if(tree[p]){ 56 for(ll i=l;i<=r;++i)printf("%c",tree[p]+'a'-1); 57 return; 58 } 59 ll mid=l+(r-l)/2; 60 print(lch(p),l,mid); 61 print(rch(p),mid+1,r); 62 } 63 int main(){ 64 scanf("%d%d",&n,&m); 65 scanf("%s",str+1); 66 build(1,1,n); 67 for(int i=1;i<=m;++i){ 68 scanf("%d%d%d",&L,&R,&op); 69 for(int &ele:cnt)ele=0; 70 get_cnt(1,1,n,L,R); 71 if(op){ 72 for(int j=1;j<=26;++j){ 73 if(cnt[j]){ 74 modify(1,n,L,L+cnt[j]-1,1,j); 75 L+=cnt[j]; 76 } 77 } 78 } 79 else{ 80 for(int j=26;j>=1;--j){ 81 if(cnt[j]){ 82 modify(1,n,L,L+cnt[j]-1,1,j); 83 L+=cnt[j]; 84 } 85 } 86 } 87 } 88 print(1,1,n); 89 return 0; 90 }