前言
相比线段树与树状数组,分块的数据结构码量不是太大,而且容易理解,但是效率不高。
又因为,线段树、树状数组与分块的联系是很大的,所适用的题型也相差不大,于是蒟蒻用分块也可以轻松地过掉这一道题。
正文
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 步骤:
-
定义块数 t=sqrt(n)
-
给每一个区间的左端点、右端点赋值;
-
记录每一个元素属于哪个区间,并把它的值加入区间的值。
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 }