真的是浅谈qwq,博主太弱,不会太多的操作的
线段树是处理区间的一个数据结构常见得有修改和求和等操作(推荐一道题:快速红包变换)
首先我们来说说线段树的优点,线段树支持一系列区间操作,但是线段树并不是每次都对每一个数进行操作,而是让上层把这些操作“克扣”下来,那么就有“克扣”条件了,如果要操作的区间完全包含当前区间,那么当前区间就可以神不知鬼不觉的把这些操作留到这里,不在向下操作,但是上面总有视察的时候啊,所以“克扣”不了多久又要原封不动的吐出来分发给下面的黎民百姓,当然了如果有些领导看不到的地方,也就不会下放下去了。
23333,蒟蒻用了一个比喻来形容线段树的原理,正事由于这个,才使得线段树的每一个操作的时间复杂度是log的。
窝自己画的,不喜勿喷。
线段树就是酱紫的,每个叶子节点代表的原区间的一个点,而非叶子节点存的是一段区间的答案,比如和或乘积之类的。
唔,窝发现窝根本不知道该怎么讲,不如先看一下模板吧,打几遍就能理解了
模板:https://www.luogu.org/problemnew/show/P3372

1 #include<iostream> 2 #include<cstdio> 3 #define ll long long 4 using namespace std; 5 const int maxn=100005; 6 int n,m; 7 struct zhw{ 8 ll lazy,val; 9 }tree[maxn<<2]; 10 int opt,x,y; 11 ll v; 12 void btree(int now,int l,int r) 13 { 14 if(l==r) 15 { 16 scanf("%lld",&tree[now].val);return; 17 } 18 int mid=(l+r)>>1; 19 btree(now<<1,l,mid),btree(now<<1|1,mid+1,r); 20 tree[now].val=tree[now<<1].val+tree[now<<1|1].val; 21 } 22 void down(int now,int l,int r) 23 { 24 if(tree[now].lazy) 25 { 26 int mid=(l+r)>>1; 27 tree[now<<1].lazy+=tree[now].lazy,tree[now<<1|1].lazy+=tree[now].lazy; 28 tree[now<<1].val+=(mid-l+1)*tree[now].lazy,tree[now<<1|1].val+=(r-mid)*tree[now].lazy; 29 tree[now].lazy=0; 30 } 31 } 32 void add(int now,int l,int r,int ql,int qr,ll v) 33 { 34 if(ql<=l&&qr>=r) 35 { 36 tree[now].lazy+=v,tree[now].val+=(r-l+1)*v;return; 37 } 38 int mid=l+r>>1; 39 down(now,l,r); //标记下放 40 if(ql<=mid)add(now<<1,l,mid,ql,qr,v);//如果不是完全被覆盖,就向下查找 41 if(qr>mid)add(now<<1|1,mid+1,r,ql,qr,v);//分出2部分,左右分别取找 42 tree[now].val=tree[now<<1].val+tree[now<<1|1].val;//这里需要更新一下,因为虽然当前点不能被所要修改的区间包含,但是所要修改的区间是它的一部分,所以它的值也会有所变化 43 } 44 ll query(int now,int l,int r,int ql,int qr) 45 { 46 if(ql<=l&&qr>=r)return tree[now].val; 47 int mid=(l+r)>>1; 48 down(now,l,r); 49 ll ans=0; 50 if(ql<=mid)ans+=query(now<<1,l,mid,ql,qr); 51 if(qr>mid)ans+=query(now<<1|1,mid+1,r,ql,qr); 52 return ans; 53 } 54 int main() 55 { 56 scanf("%d%d",&n,&m); 57 btree(1,1,n); 58 while(m--)//2个操作,区间修改和区间求和 59 { 60 scanf("%d%d%d",&opt,&x,&y); 61 if(opt==1) 62 { 63 scanf("%lld",&v); 64 add(1,1,n,x,y,v); 65 } 66 else 67 printf("%lld ",query(1,1,n,x,y)); 68 } 69 return 0; 70 }
唔,大概就是这样吧,其实线段树模板还是很好理解的。我最开始学线段树的时候写了一个区间修改点查询的模板,调了一天,┓( ´∀` )┏
下面来看一些例题
1. 洛谷2574 xor的艺术
AKN觉得第一题太水了,不屑于写第一题,所以他又玩起了新的游戏。在游戏中,他发现,这个游戏的伤害计算有一个规律,规律如下
1、 拥有一个伤害串为长度为n的01串。
2、 给定一个范围[l,r],伤害为伤害串的这个范围内中1的个数
3、 会被随机修改伤害串中的数值,修改的方法是把[l,r]中的所有数xor上1
AKN想知道一些时刻的伤害,请你帮助他求出这个伤害
裸的线段树qwq,01异或就是取反,只需要记录个数,每次异或就是把区间中1的个数变成区间长度-1的个数即可,代码:

1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int maxn=2e5+2; 5 int n,m; 6 struct node{ 7 int val,lazy; 8 }; 9 node tree[maxn<<2]; 10 void btree(int now,int l,int r) 11 { 12 if(l==r) 13 { 14 scanf("%1d",&tree[now].val); 15 return; 16 } 17 int mid=(l+r)>>1; 18 btree(now<<1,l,mid); 19 btree(now<<1|1,mid+1,r); 20 tree[now].val=tree[now<<1].val+tree[now<<1|1].val; 21 } 22 void down(int now,int l,int r) 23 { 24 if(tree[now].lazy) 25 { 26 int mid=(l+r)>>1; 27 tree[now<<1].lazy^=tree[now].lazy; 28 tree[now<<1|1].lazy^=tree[now].lazy; 29 tree[now<<1].val=(mid-l+1)-tree[now<<1].val; 30 tree[now<<1|1].val=(r-mid)-tree[now<<1|1].val; 31 tree[now].lazy=0; 32 } 33 } 34 int p,x,y;//抑或0是自己,抑或1是本身 35 //抑或上一,0——>1,1——>0,所以1的个数是区间长度减去1的个数 36 void add(int now,int l,int r,int ql,int qr) 37 { 38 if(ql<=l&&qr>=r) 39 { 40 tree[now].lazy^=1; 41 tree[now].val=(r-l+1)-tree[now].val; 42 return; 43 } 44 int mid=(l+r)>>1; 45 down(now,l,r); 46 if(ql<=mid)add(now<<1,l,mid,ql,qr); 47 if(qr>mid)add(now<<1|1,mid+1,r,ql,qr); 48 tree[now].val=tree[now<<1].val+tree[now<<1|1].val; 49 } 50 int query(int now,int l,int r,int ql,int qr) 51 { 52 if(ql<=l&&qr>=r)return tree[now].val; 53 int mid=(l+r)>>1; 54 int ans=0; 55 down(now,l,r); 56 if(ql<=mid)ans+=query(now<<1,l,mid,ql,qr); 57 if(qr>mid)ans+=query(now<<1|1,mid+1,r,ql,qr); 58 return ans; 59 } 60 int main() 61 { 62 scanf("%d%d",&n,&m); 63 btree(1,1,n); 64 for(int i=1;i<=m;++i) 65 { 66 scanf("%d",&p); 67 scanf("%d%d",&x,&y); 68 if(p)cout<<query(1,1,n,x,y)<<endl; 69 else add(1,1,n,x,y); 70 } 71 }
2.洛谷P2787 语文1(chin1)- 理理思维
考试开始了,可是蒟蒻HansBug脑中还是一片空白。哦不!准确的说是乱七八糟的。现在首要任务就是帮蒟蒻HansBug理理思维。假设HansBug的思维是一长串字符串(字符串中包含且仅包含26个字母),现在的你,有一张神奇的药方,上面依次包含了三种操作:
-
获取第x到第y个字符中字母k出现了多少次
-
将第x到第y个字符全部赋值为字母k
-
将第x到第y个字符按照A-Z的顺序排序
你欣喜若狂之时,可是他脑细胞和RP已经因为之前过度紧张消耗殆尽,眼看试卷最后还有一篇800字的作文呢,所以这个关键的任务就交给你啦!
唔,窝感觉这是一道很好的题,因为我开始看到排序的操作直接就懵逼了,后来看了别人的题解才知道怎么做,确实很巧妙的感觉,在数据结构里开一个大熊啊为26的数组,记录每个字符的个数,而排序就用第一个和第二个操作来完成,先求出每个字符的个数,然后从0到25一次进行区间覆盖,覆盖的区间的长度就是字符出现的次数,这样就可以假装排序了,23333

1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 const int maxn=65536; 6 char s[maxn]; 7 int lazy[maxn<<2]; 8 int n,m; 9 struct zhw{ 10 int cnt[26]; 11 zhw(){memset(cnt,0,sizeof(cnt));}; 12 }tree[maxn<<2]; 13 zhw operator +(zhw a,zhw b) 14 { 15 zhw c; 16 for(int i=0;i<26;++i)c.cnt[i]=a.cnt[i]+b.cnt[i]; 17 return c; 18 } 19 void btree(int now,int l,int r) 20 { 21 if(l==r) 22 { 23 tree[now].cnt[s[l]-'A']=1;return; 24 } 25 int mid=(l+r)>>1; 26 btree(now<<1,l,mid),btree(now<<1|1,mid+1,r); 27 tree[now]=tree[now<<1]+tree[now<<1|1]; 28 } 29 int flag,x,y; 30 char c; 31 void down(int now,int l,int r) 32 { 33 if(lazy[now]!=-1) 34 { 35 lazy[now<<1]=lazy[now],lazy[now<<1|1]=lazy[now]; 36 tree[now<<1]=zhw(),tree[now<<1|1]=zhw(); 37 //因为是很恶心的覆盖操作的lazy,所以先清空后赋值来搞 38 tree[now<<1].cnt[lazy[now]]=l,tree[now<<1|1].cnt[lazy[now]]=r; 39 lazy[now]=-1; 40 } 41 } 42 zhw query(int now,int l,int r,int ql,int qr) 43 { 44 if(ql<=l&&qr>=r)return tree[now]; 45 zhw ans; 46 int mid=(l+r)>>1; 47 down(now,mid-l+1,r-mid); 48 if(ql<=mid)ans=ans+query(now<<1,l,mid,ql,qr); 49 if(qr>mid)ans=ans+query(now<<1|1,mid+1,r,ql,qr); 50 return ans;//已经重载过加号了呀,所以ans只是一个用来记录数量的变量 51 } 52 void change(int now,int l,int r,int ql,int qr,int k) 53 { 54 if(ql<=l&&qr>=r) 55 { 56 tree[now]=zhw();lazy[now]=k,tree[now].cnt[k]=r-l+1;return; 57 } 58 int mid=(l+r)>>1; 59 down(now,mid-l+1,r-mid); 60 if(ql<=mid)change(now<<1,l,mid,ql,qr,k); 61 if(qr>mid)change(now<<1|1,mid+1,r,ql,qr,k); 62 tree[now]=tree[now<<1]+tree[now<<1|1]; 63 } 64 int main() 65 { 66 scanf("%d%d",&n,&m);scanf("%s",s+1); 67 memset(lazy,-1,sizeof(lazy)); 68 for(int i=n;i>=1;i--) 69 if(s[i]>='a'&&s[i]<='z')s[i]=s[i]-'a'+'A'; 70 btree(1,1,n); 71 while(m--) 72 { 73 scanf("%d",&flag); 74 if(flag==1) 75 { 76 scanf("%d%d",&x,&y);cin>>c; 77 if(c<='z'&&c>='a')c=c-'a'+'A'; 78 printf("%d ",query(1,1,n,x,y).cnt[c-'A']); 79 } 80 else if(flag==2) 81 { 82 scanf("%d%d",&x,&y);cin>>c; 83 if(c<='z'&&c>='a')c=c-'a'+'A'; 84 change(1,1,n,x,y,c-'A'); 85 } 86 else{ 87 scanf("%d%d",&x,&y); 88 zhw tmp=query(1,1,n,x,y); 89 int l=0,r=x-1; 90 for(int i=0;i<26;++i) 91 { 92 if(tmp.cnt[i]!=0) 93 { 94 l=r+1,r=l+tmp.cnt[i]-1; 95 change(1,1,n,l,r,i); 96 } 97 } 98 } 99 } 100 return 0; 101 }
3.洛谷P1198 [JSOI2008]最大数
现在请求你维护一个数列,要求提供以下两种操作:
1、 查询操作。
语法:Q L
功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值。
限制:L不超过当前数列的长度。(L>=0)
2、 插入操作。
语法:A n
功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列的末尾。
限制:n是整数(可能为负数)并且在长整范围内。
注意:初始时数列是空的,没有一个数。
这也是很裸的线段树,操作1就是区间查询,操作2就是点修改,但是要注意区间长度始终要设成最大值,不能动态的变,我开始写的时候就挂在这里了qwq

1 //一个add,一个query,简单的线段树吧 2 #include<iostream> 3 #include<cstdio> 4 using namespace std; 5 const int maxn=200005; 6 int m,d; 7 struct Ray{ 8 int val; 9 }tree[maxn<<2]; 10 char c; 11 int x; 12 int n; 13 void btree(int now,int l,int r) 14 { 15 tree[now].val=2147483648; 16 if(l==r)return; 17 int mid=(l+r)>>1; 18 btree(now<<1,l,mid),btree(now<<1|1,mid+1,r); 19 } 20 int query(int now,int l,int r,int ql,int qr) 21 { 22 if(ql<=l&&qr>=r) 23 { 24 // cout<<now<<" "<<tree[now].val<<endl; 25 return tree[now].val; 26 } 27 int mid=(l+r)>>1; 28 int ans=2147483648; 29 if(ql<=mid)ans=max(ans,query(now<<1,l,mid,ql,qr)); 30 if(qr>mid)ans=max(ans,query(now<<1|1,mid+1,r,ql,qr)); 31 return ans; 32 } 33 void add(int now,int l,int r,int ql,int qr,int v) 34 { 35 if(ql<=l&&qr>=r) 36 { 37 tree[now].val=v; 38 return; 39 } 40 int mid=(l+r)>>1; 41 if(ql<=mid)add(now<<1,l,mid,ql,qr,v); 42 else add(now<<1|1,mid+1,r,ql,qr,v); 43 tree[now].val=max(tree[now<<1].val,tree[now<<1|1].val); 44 } 45 int ans=0; 46 int main() 47 { 48 scanf("%d%d",&m,&d); 49 btree(1,1,200000); 50 for(int i=1;i<=m;++i) 51 { 52 cin>>c;scanf("%d",&x); 53 if(c=='Q'&&x==0) 54 { 55 printf("0 ");continue; 56 } 57 if(c=='Q')printf("%d ",ans=query(1,1,200000,n-x+1,n)); 58 else{ 59 x+=ans,x%=d; 60 add(1,1,200000,n+1,n+1,x); 61 n++; 62 } 63 } 64 return 0; 65 }