浅谈分块兼LuoguP3372 【模板】线段树 1 题解

前言

相比线段树与树状数组,分块的数据结构码量不是太大,而且容易理解,但是效率不高

又因为,线段树、树状数组与分块的联系是很大的,所适用的题型也相差不大,于是蒟蒻用分块也可以轻松地过掉这一道题。

正文

1. 引例

显然,这道题用到的是区间修改和区间查询(求和),当然还有建立线段树

在线段树里,是这样写的:(仅是蒟蒻的写法)

(1)区间修改(这码量…

 1 void spread(int t){     //打延迟标记
 2     if(tree[t].mark)
 3     {
 4         tree[t*2].val+=tree[t].mark*(tree[t*2].r-tree[t*2].l+1);
 5         tree[t*2+1].val+=tree[t].mark*(tree[t*2+1].r-tree[t*2+1].l+1);
 6         tree[t*2].mark+=tree[t].mark;
 7         tree[t*2+1].mark+=tree[t].mark;
 8         tree[t].mark=0;
 9     }
10 }
11 
12 void change(int t,int x,int y,int k)    //维护线段树
13 {
14     if(x<=tree[t].l && y>=tree[t].r)
15     {
16         tree[t].val+=(long long)k*(tree[t].r-tree[t].l+1);
17         tree[t].mark+=k;
18         return;
19     }
20     spread(t);
21     int mid=tree[t].l+tree[t].r>>1;
22     if(x<=mid) change(t*2,x,y,k);
23     if(y>mid) change(t*2+1,x,y,k);
24     tree[t].val=tree[t*2].val+tree[t*2+1].val;   
25 }

 

(2)区间查询

 1 long long ask(int t,int x,int y)    //查询
 2 {
 3     if(x<=tree[t].l && y>=tree[t].r) return tree[t].val;
 4     spread(t);
 5     int mid=tree[t].l+tree[t].r>>1;
 6     long long ans=0;
 7     if(x<=mid) ans+=ask(t*2,x,y);
 8     if(y>mid) ans+=ask(t*2+1,x,y);
 9     return ans;
10 }

 

(3)建树

 1 void bulid(int t,int l,int r)
 2 {
 3     tree[t].l=l;tree[t].r=r;
 4     if(l==r)
 5     {
 6         tree[t].val=a[l];
 7         return;
 8     }
 9     int mid=l+r>>1;
10     bulid(t*2,l,mid);   //左子树
11     bulid(t*2+1,mid+1,r);   //右子树
12     tree[t].val=tree[t*2].val+tree[t*2+1].val;  //得到孩子的值后求自己的值
13 } 

 

可以看出,在维护一棵线段树的时候,是很麻烦的。

2.浅谈分块

现在,我们来研究分块的做法。

(1)分块的思想:

把区间分成几个块,再分段处理。其中,大段维护、局部朴素。

就像图上,我用红色的线把区间分块,蓝色的数是元素。

一般地,我们把一个有 n 个元素的区间分成 √n个块,如果还有不足一块的元素就单独分成一块。

当然,应对与不同的题目,为了简化时间复杂度,分成的块数还有 ∛n,n^(3/4)$等等。

每一块都由一个结构体存储,每一个结构体最基本要包含:左端点、右端点、区间值。

其中区间值对于不同的题目而言是不同的数,就此题而言,区间值是该区间所有元素之和。

(2)算法代码:

1.分块

1.1 步骤:

  1. 定义块数 t=sqrt(n)

  2. 给每一个区间的左端点、右端点赋值;

  3. 记录每一个元素属于哪个区间,并把它的值加入区间的值。

1.2 代码:

 1 void Blocking()
 2 {
 3     int t=sqrt(n);
 4     for(int i=1;i<=t;i++)
 5     {
 6         blo[i].l=(i-1)*sqrt(n)+1;
 7         blo[i].r=i*sqrt(n);
 8     }
 9     if(blo[t].r<n)
10     {
11         blo[t+1].l=blo[t].r+1;
12         blo[t+1].r=n;
13     }
14     for(int i=1;i<=t+1;i++)
15         for(int j=blo[i].l;j<=blo[i].r;j++)
16         {
17             pos[j]=i;
18             blo[i].val+=a[j];
19         }
20 }

 

2.维护修改

2.1 步骤

当左端点与右端点同段时:

直接暴力修改每一个元素的值。

当左端点与右端点异段时:

如上图所述,我们选定了闭区间 [7,14][7,14] 将要操作。

在这个区间内,有大段 [9,12][9,12] 和小段 [7,8][7,8] 、 [13,14][13,14]

对于大段,我们要采取算法时间复杂度高于朴素的算法,怎么实现呢?

对于每一段,我们添加一个元素 add ,作用是记录当该区间被整段修改时加了多少(也可以为负数),这样就不用对每一个元素修改,简化了时间复杂度。

2.2 代码

 1 void change(int x,int y,int k)
 2 {
 3     int p=pos[x];
 4     int q=pos[y];
 5     if(p==q)
 6     {
 7         for(int i=x;i<=y;i++)
 8             a[i]+=k;
 9         blo[p].val+=(y-x+1)*k;
10     }
11     else
12     {
13         for(int i=p+1;i<=q-1;i++)
14             blo[i].add+=k;
15         for(int i=x;i<=blo[p].r;i++)
16             a[i]+=k;
17         blo[p].val+=(blo[p].r-x+1)*k;
18         for(int i=blo[q].l;i<=y;i++)
19             a[i]+=k;
20         blo[q].val+=(y-blo[q].l+1)*k;
21     }
22 }

 

3.询问

3.1 步骤:

为了提高时间复杂度,我们加入了 add 元素,所以在询问的时候会更加复杂。

当左端点与右端点同段时:

暴力累加每一个元素的值。

当左端点与右端点异段时:

还是这幅图,不过这次我需要查询区间内每一个数的值,

这时,我们同修改一样,对整段和小段分别处理。

整段:这里又不得不提起区间值 val 了,

val 在某种程度上记录了该区间的值。但是,在整个区间修改的时候,我们并没有修改 val 的值,而是修改了 add的值。

所以,我们还要把val计算进去。

小段:朴素地加每一个数的值,并加上所在区间的 add 。

Q :为什么还有加上 add 呢?在查询时该区间是小段而不是整段。

A :虽然在查询时是小段,但是在区间修改的时候,有可能是整段,只修改了 add ,所以还要加上 add

3.2 代码

 1 int ask(int x,int y)
 2 {
 3     int sum=0;
 4     int p=pos[x];
 5     int q=pos[y];
 6     if(p==q)
 7     {
 8         for(int i=x;i<=y;i++)
 9             sum+=a[i];
10         sum+=(y-x+1)*blo[p].add;
11     }
12     else
13     {
14         for(int i=p+1;i<=q-1;i++)
15             sum+=blo[i].val+(blo[i].r-blo[i].l+1)*blo[i].add;
16         for(int i=x;i<=blo[p].r;i++)
17             sum+=a[i];
18         sum+=(blo[p].r-x+1)*blo[p].add;
19         for(int i=blo[q].l;i<=y;i++)
20             sum+=a[i];
21         sum+=(y-blo[q].l+1)*blo[q].add;
22     }
23     return sum;
24 }

 

AC_总代码:

 1 #include<bits/stdc++.h>
 2 #pragma GCC optimize(3)
 3 #define ll long long
 4 using namespace std;
 5 struct Block
 6 {
 7     ll l,r,val,add;
 8 }blo[100005];
 9 ll a[100005],pos[100005],n,m,op,x,y,k;
10 void Blocking()
11 {
12     int t=sqrt(n);
13     for(int i=1;i<=t;i++)
14     {
15         blo[i].l=(i-1)*sqrt(n)+1;
16         blo[i].r=i*sqrt(n);
17     }
18     if(blo[t].r<n)
19     {
20         blo[t+1].l=blo[t].r+1;
21         blo[t+1].r=n;
22     }
23     for(int i=1;i<=t+1;i++)
24         for(int j=blo[i].l;j<=blo[i].r;j++)
25         {
26             pos[j]=i;
27             blo[i].val+=a[j];
28         }
29 }
30 void change(ll x,ll y,ll k)
31 {
32     int p=pos[x];
33     int q=pos[y];
34     if(p==q)
35     {
36         for(int i=x;i<=y;i++)
37             a[i]+=k;
38         blo[p].val+=(y-x+1)*k;
39     }
40     else
41     {
42         for(int i=p+1;i<=q-1;i++)
43             blo[i].add+=k;
44         for(int i=x;i<=blo[p].r;i++)
45             a[i]+=k;
46         blo[p].val+=(blo[p].r-x+1)*k;
47         for(int i=blo[q].l;i<=y;i++)
48             a[i]+=k;
49         blo[q].val+=(y-blo[q].l+1)*k;
50     }
51 }
52 ll ask(int x,int y)
53 {
54     ll sum=0;
55     int p=pos[x];
56     int q=pos[y];
57     if(p==q)
58     {
59         for(int i=x;i<=y;i++)
60             sum+=a[i];
61         sum+=(y-x+1)*blo[p].add;
62     }
63     else
64     {
65         for(int i=p+1;i<=q-1;i++)
66             sum+=blo[i].val+(blo[i].r-blo[i].l+1)*blo[i].add;
67         for(int i=x;i<=blo[p].r;i++)
68             sum+=a[i];
69         sum+=(blo[p].r-x+1)*blo[p].add;
70         for(int i=blo[q].l;i<=y;i++)
71             sum+=a[i];
72         sum+=(y-blo[q].l+1)*blo[q].add;
73     }
74     return sum;
75 }
76 int main()
77 {
78     std::ios::sync_with_stdio(false);
79     cin>>n>>m;
80     for(int i=1;i<=n;i++)
81         cin>>a[i];
82     Blocking();
83     for(int i=1;i<=m;i++)
84     {
85         cin>>op;
86         if(op==1)
87         {
88             cin>>x>>y>>k;
89             change(x,y,k);
90         }
91         else if(op==2)
92         {
93             cin>>x>>y;
94             cout<<ask(x,y)<<endl;
95         }
96     }
97     return 0;
98 }

 

posted @ 2019-08-04 12:51  iDarkForest  阅读(191)  评论(0编辑  收藏  举报