浅谈线段树

  真的是浅谈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\n",query(1,1,n,x,y));
68     }
69     return 0;
70 } 
View Code

  唔,大概就是这样吧,其实线段树模板还是很好理解的。我最开始学线段树的时候写了一个区间修改点查询的模板,调了一天,┓( ´∀` )┏

  下面来看一些例题

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 } 
View Code

2.洛谷P2787 语文1(chin1)- 理理思维

考试开始了,可是蒟蒻HansBug脑中还是一片空白。哦不!准确的说是乱七八糟的。现在首要任务就是帮蒟蒻HansBug理理思维。假设HansBug的思维是一长串字符串(字符串中包含且仅包含26个字母),现在的你,有一张神奇的药方,上面依次包含了三种操作:

  1. 获取第x到第y个字符中字母k出现了多少次

  2. 将第x到第y个字符全部赋值为字母k

  3. 将第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\n",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 }
View Code

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\n");continue;
56         }
57         if(c=='Q')printf("%d\n",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 }
View Code

 

转载于:https://www.cnblogs.com/yuelian/p/8745056.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值