算法学习:线段树
线段树,个人理解,生成一棵二叉树,树上的节点表示区间的答案,因为二叉树的性质天然就将树分成两半,所以可以用每个节点存左半边右半边,然后这样子就可以保证效率。
具体讲解是看这位大大的博客,图解和语言都很详细。
https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html
这个里面树是靠结构体存储了一个真正意义上节点为包含左右区间范围及其答案的节点
然后我的代码是参考了洛谷的一个题解的代码,所以是用询问函数上的范围取代了,但是这两者区别并不是很大,因为p这个节点的数字,通过二进制来看,天然的就可以求取出他所代表的区间。
附代码,对树的解释都在注释上:
1 #include<cstdio> 2 #define ll long long 3 #define MAXN 100010 4 int a[MAXN]; 5 int ans[MAXN]; 6 int t[MAXN]; 7 int tag[MAXN]; 8 inline int ls(int p){return p<<1;}//找左节点 9 inline int rs(int p){return p<<1|1;}//找右节点 10 void push_up(int p)//更新操作 11 { 12 ans[p]=ans[ls(p)]+ans[rs(p)]; 13 return; 14 } 15 void build(ll p,ll l,ll r)//建树 16 { 17 if(l==r)//如果是底层叶节点 18 { //l==r区间,也就是这个节点本身 19 ans[p]=a[l]; //返回叶节点 20 return; //那么就是当前数组保存此节点 21 } 22 ll mid=(l+r)>>1; 23 build(ls(p),l,mid);//使p的左孩子包含(l---mid)的值 24 build(rs(p),mid+1,r); 25 push_up(p); 26 //左右结点的值已经通过递归得到 27 //可以根据左右确定(更新)自己的值 28 return; 29 } 30 void f(int p,int l,int r,int k) 31 { 32 tag[p]+=k; 33 //给当前节点加上之前节点的懒标记 34 //这样子懒标记不会下传 35 //但是之后还可以用,不会影响其他的查询 36 ans[p]+=(r+1-l)*k; 37 //更新树上的值 38 //这样就不会向下扩散 39 //保证效率 40 } 41 void push_down(int p,int l,int r) 42 { 43 ll mid=(l+r)>>1; 44 //向下更新 45 f(ls(p),l,mid,tag[p]); 46 f(rs(p),mid+1,r,tag[p]); 47 tag[p]=0; 48 //因为这个点的懒标记已经下传 49 //所以这个点对下面来说 50 //已经没有了需要更新的值 51 //懒标记清0 52 } 53 //我个人最难理解的一步 54 //对区间的更新 55 //nr,nl需要查询的期间 56 //l,r当前节点 57 void update(ll nl,ll nr,ll l,ll r,ll p,ll k) 58 { 59 if(nl<=l&&r<=nr) 60 //若当前区间全部在所查询区间 61 //直接进行更新 62 { 63 ans[p]+=(r+1-l)*k; 64 tag[p]+=k; 65 return ; 66 } 67 push_down(p,l,r); 68 //因为需要保证下面两个节点的正确性 69 //所以要向下进行更新 70 ll mid=(r+l)>>1; 71 if(nl<=mid) update(nl,nr,l,mid,ls(p),k); 72 if(nr>mid) update(nl,nr,mid+1,r,rs(p),k); 73 //根据区间对子节点进行更新 74 //先将子节点更新完成 75 push_up(p); 76 return; 77 } 78 ll query(ll nl,ll nr,ll l,ll r,ll p) 79 { 80 ll res=0; 81 if(nl<=l&&r<=nr) 82 { 83 return ans[p]; 84 } 85 ll mid=(r+l)>>1; 86 push_down(p,l,r); 87 if(nl<=mid) res+=query(nl,nr,l,mid,ls(p)); 88 if(nr>mid) res+=query(nl,nr,mid+1,r,rs(p)); 89 return res; 90 } 91 int main() 92 { 93 int n,m; 94 scanf("%d%d",&n,&m); 95 for(int i=1;i<=n;i++) 96 { 97 scanf("%d",&a[i]); 98 } 99 build(1,1,n); 100 while(m--) 101 { 102 int p; 103 scanf("%d",&p); 104 switch(p) 105 { 106 case 1: 107 { 108 int l,r,k; 109 scanf("%d%d%d",&l,&r,&k); 110 update(l,r,1,n,1,k); 111 //可以理解为对第一个节点进行更新 112 //第一个节点包括的区间就是1~n 113 break; //修改操作 114 } 115 case 2: 116 { 117 int l,r; 118 scanf("%d%d",&l,&r); 119 printf("%lld\n",query(l,r,1,n,1));//输出值的操作 120 break;//1,n代表的是第一个节点,代表的是具有从1~n的值的数字 121 } 122 } 123 } 124 return 0; 125 }
1 #include<cstdio> 2 #define ll long long 3 #define MAXN 100010 4 int a[MAXN]; 5 int ans[MAXN]; 6 int t[MAXN]; 7 int tag[MAXN]; 8 inline int ls(int p){return p<<1;} 9 inline int rs(int p){return p<<1|1;} 10 void push_up(int p) 11 { 12 ans[p]=ans[ls(p)]+ans[rs(p)]; 13 return; 14 } 15 void build(ll p,ll l,ll r) 16 { 17 if(l==r) 18 { 19 ans[p]=a[l]; 20 return; 21 } 22 ll mid=(l+r)>>1; 23 build(ls(p),l,mid); 24 build(rs(p),mid+1,r); 25 push_up(p); 26 return; 27 } 28 void f(int p,int l,int r,int k) 29 { 30 tag[p]+=k; 31 ans[p]+=(r+1-l)*k; 32 } 33 void push_down(int p,int l,int r) 34 { 35 ll mid=(l+r)>>1; 36 f(ls(p),l,mid,tag[p]); 37 f(rs(p),mid+1,r,tag[p]); 38 tag[p]=0; 39 } 40 void update(ll nl,ll nr,ll l,ll r,ll p,ll k) 41 { 42 push_down(p,l,r); 43 if(nl<=l&&r<=nr) 44 { 45 ans[p]+=(r+1-l)*k; 46 tag[p]+=k; 47 return ; 48 } 49 ll mid=(r+l)>>1; 50 if(nl<=mid) update(nl,nr,l,mid,ls(p),k); 51 if(nr>mid) update(nl,nr,mid+1,r,rs(p),k); 52 push_up(p); 53 return; 54 } 55 ll query(ll nl,ll nr,ll l,ll r,ll p) 56 { 57 ll res=0; 58 push_down(p,l,r); 59 if(nl<=l&&r<=nr) 60 { 61 return ans[p]; 62 } 63 ll mid=(r+l)>>1; 64 if(nl<=mid) res+=query(nl,nr,l,mid,ls(p)); 65 if(nr>mid) res+=query(nl,nr,mid+1,r,rs(p)); 66 return res; 67 } 68 int main() 69 { 70 int n,m; 71 scanf("%d%d",&n,&m); 72 for(int i=1;i<=n;i++) 73 { 74 scanf("%d",&a[i]); 75 } 76 build(1,1,n); 77 while(m--) 78 { 79 int p; 80 scanf("%d",&p); 81 switch(p) 82 { 83 case 1: 84 { 85 int l,r,k; 86 scanf("%d%d%d",&l,&r,&k); 87 update(l,r,1,n,1,k); 88 break; 89 } 90 case 2: 91 { 92 int l,r; 93 scanf("%d%d",&l,&r); 94 printf("%lld\n",query(l,r,1,n,1)); 95 break; 96 } 97 } 98 } 99 return 0; 100 } 101
例题:
洛谷P1198【JSOI 2008】最大的数
【题意】两种操作:
1:查询从末尾倒数L个数中最大值
2:将输入数n加上上次的查询值(上次没有查询+0)插入末尾
【思路】1:查询直接在已有线段树查询
2:插入,将整棵线段树视作所有值都为0的树,插入时视作,改变size(整棵树中元素个数)+1的位置加上这个值,然后线段树操作在1~Mmax上更新
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #define MAXN 200007 5 #define MINN (-1)*((1<<31)-1) 6 using namespace std; 7 int tree[MAXN<<2]; 8 int mod,t=0; 9 int n=0; 10 void init() 11 { 12 for(int i=1;i<MAXN;i++) 13 { 14 tree[i]=MINN; 15 } 16 return; 17 } 18 void push_up(int p) 19 { 20 int maxn=max(tree[p<<1],tree[p<<1|1]); 21 tree[p]=max(maxn,tree[p]); 22 return ; 23 } 24 int add(int rt,int l,int r,int p,int val) 25 { 26 if(l==r) 27 { 28 tree[rt]=val; 29 return 0; 30 } 31 int mid=(l+r)>>1; 32 if(p<=mid) 33 add(rt<<1,l,mid,p,val); 34 else 35 add(rt<<1|1,mid+1,r,p,val); 36 push_up(rt); 37 } 38 int quer(int nl,int nr,int l,int r,int p) 39 { 40 if(nl<=l&&r<=nr) 41 { 42 return tree[p]; 43 } 44 int res=0; 45 int mid=(l+r)>>1; 46 if(nl<=mid) 47 res=quer(nl,nr,l,mid,p<<1); 48 if(nr>mid) 49 res=max(res,quer(nl,nr,mid+1,r,p<<1|1)); 50 return res; 51 } 52 int main() 53 { 54 int m; 55 scanf("%d%d",&m,&mod); 56 init(); 57 getchar(); 58 getchar(); 59 for(int i=1;i<=m;i++) 60 { 61 char c; 62 int d; 63 scanf("%c %d",&c,&d); 64 getchar(); 65 getchar(); 66 int tmp=(t+d)%mod; 67 //printf("tmp=%d\n",tmp); 68 switch(c) 69 { 70 case 'A': 71 n++; 72 add(1,1,m,n,tmp); 73 break; 74 case 'Q': 75 t=(quer(n+1-d,n,1,m,1))%mod; 76 printf("%d\n",t); 77 break; 78 } 79 } 80 return 0; 81 }
洛谷P2023 [AHOI2009] 维护序列
【题意】两种操作:
1:给区间加一个数
2:给区间乘一个数
3:询问区间和
【思路】线段树常用方法,开两个懒标记,一个储存乘,一个储存加,在乘下传的时候考虑乘标记对加标记的影响
#include<cstdio> #include<iostream> #define ll long long #define MAXN 100010 using namespace std; ll a[MAXN]; ll ans[MAXN<<2]; ll res; ll tag_m[MAXN<<2],tag_a[MAXN<<2]; ll mod,n,m; inline int ls(int p){return p<<1;} inline int rs(int p){return p<<1|1;} void push_up(int p) { ans[p]=(ans[ls(p)]+ans[rs(p)])%mod; return; } void build(ll p,ll l,ll r) { if(l==r) { ans[p]=a[l]; return; } ll mid=(l+r)>>1; build(ls(p),l,mid); build(rs(p),mid+1,r); push_up(p); return; } void push_down(int p,int l,int r,int op) { if(tag_m[p]==1&&tag_a[p]==0) return; ll lp=ls(p),rp=rs(p); if(l!=r) { tag_m[rp]=tag_m[rp]*tag_m[p]%mod; tag_m[lp]=tag_m[lp]*tag_m[p]%mod; tag_a[lp]=((tag_a[lp]*tag_m[p])%mod+tag_a[p])%mod; tag_a[rp]=((tag_a[rp]*tag_m[p])%mod+tag_a[p])%mod; } ans[p]=(ans[p]*tag_m[p]%mod+tag_a[p]*(r-l+1)%mod)%mod; tag_m[p]=1;tag_a[p]=0; } void update(ll nl,ll nr,ll l,ll r,ll p,ll k,int op) { push_down(p,l,r,op); if(nl<=l&&r<=nr) { switch(op) { case 1:tag_m[p]=(tag_m[p]*k)%mod,tag_a[p]=(tag_a[p]*k)%mod;break; case 2:tag_a[p]=(tag_a[p]+k)%mod;break; } return ; } ll mid=(r+l)>>1; if(nl<=mid) update(nl,nr,l,mid,ls(p),k,op); if(nr>mid) update(nl,nr,mid+1,r,rs(p),k,op); push_down(ls(p),l,mid,op); push_down(rs(p),mid+1,r,op); push_up(p); return; } ll query(ll nl,ll nr,ll l,ll r,ll p,int op) { ll res=0; push_down(p,l,r,op); if(nl<=l&&r<=nr) { return ans[p]; } ll mid=(r+l)>>1; if(nl<=mid) res+=query(nl,nr,l,mid,ls(p),op); if(nr>mid) res+=query(nl,nr,mid+1,r,rs(p),op); return res; } int main() { scanf("%lld%lld",&n,&mod); for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(int i=1;i<=4*n;i++) { tag_m[i]=1; } build(1,1,n); scanf("%lld",&m); for(int i=1;i<=m;i++) { int op,l,r,k; scanf("%d",&op); switch(op) { case 1: case 2:scanf("%d%d%d",&l,&r,&k); update(l,r,1,n,1,k,op); break; case 3:scanf("%d%d",&l,&r); res=query(l,r,1,n,1,op)%mod; printf("%lld\n",res); break; } } }