分块
分 块
模板题
没有专门模板,就直接用线段树1啦,其实都一样
数据结构讲解
说句良心话,分块真的不难理解,甚至比树状数组,线段树更容易,只是效率偏低
一句话概括分块:大段维护,局部朴素
怎么理解呢?
以模板题(区间求和,区间更新)为例,将原数列划分成t个块,每个块的大小不超过根号n,我们预处理出每一个块内的和,另外开一个add[]
(类似于线段树懒标记,但没有下传操作),存储每个块内增加的值(其实这里讲的不是很清楚,等下就明白了)
预处理
变量说明:
int n , m , t;
//n,m(见题目),t:段(块)的数量
ll a[nn] , sum[nn] , add[nn];
//a:见题目,sum:每一段(块)内a的和,add:等下讲
int L[nn] , R[nn];
//L:每一段(块)的左边界,R:每一段的右边界
int pos[nn];
//pos[i]:第i个点属于哪一个块
void Init() {
t = sqrt(n);//此时的t既是段的数量,又是段的长度
for(int i = 1 ; i <= t ; i++)
L[i] = (i - 1) * t + 1,//这里可以自行推导一下
R[i] = i * t;
if(R[t] < n) {//处理剩下的点
++t;
L[t] = R[t - 1] + 1 , R[t] = n;
}
for(int i = 1 ; i <= t ; i++)
for(int j = L[i] ; j <= R[i] ; j++)
pos[j] = i , sum[i] += a[j];
}
区间修改
对于1操作(区间修改):[l,r]上的每一个数加上dat(变量与题目不同)
-
若l,r同属一个分块,直接暴力,将
a[l~r]
加上dat -
否则,设l处于第p个分块,r属于第q个分块,
-
\(对于i\in[p+1,q+1],add_i+=dat\)
-
对于开头,结尾不足一整段的两部分,直接用朴素方法更新即可
-
看代码:
void change(int l , int r , ll dat) {
int p = pos[l] , q = pos[r];
if(p == q) {//l,r同属一个分块
for(int i = l ; i <= r ; i++)//暴力
a[i] += dat;
sum[p] += dat * (r - l + 1);//勿忘更新sum
return;
}
for(int i = l ; i <= R[p] ; i++)//对于开头不足一段的部分,暴力
a[i] += dat;
sum[p] += (R[p] - l + 1) * dat;
for(int i = p + 1 ; i <= q - 1 ; i++)//中间,加到add即可,注意不需要更新sum,更具体的原因,查询部分会知道
add[i] += dat;
for(int i = L[q] ; i <= r ; i++)//结尾不足一段,暴力
a[i] += dat;
sum[q] += (r - L[q] + 1) * dat;
}
到此,你应该理解add数组的用处了
区间查询
基本原理同区间修改,也是头尾不足一段的直接暴力,中间整段查询,这里不再赘述
ll query(int l , int r) {
int p = pos[l] , q = pos[r];
ll ans = 0;
if(p == q) {
for(int i = l ; i <= r ; i++)
ans += a[i];
ans += add[p] * (r - l + 1);
return ans;
}
for(int i = l ; i <= R[p] ; i++)
ans += a[i];
ans += add[p] * (R[p] - l + 1);
for(int i = p + 1 ; i <= q - 1 ; i++)
ans += sum[i] + add[i] * (R[i] - L[i] + 1);
for(int i = L[q] ; i <= r ; i++)
ans += a[i];
ans += add[q] * (r - L[q] + 1);
return ans;
}
模板题代码
#include <iostream>
#include <cstdio>
#include <cmath>
#define nn 100010
#define ll long long
using namespace std;
ll read() {
ll re = 0;
bool sig = false;
char c;
do
if((c = getchar()) == '-')sig = true;
while(c < '0' || c > '9');
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0' , c = getchar();
return sig ? -re : re;
}
int n , m , t;
ll a[nn] , sum[nn] , add[nn];
int L[nn] , R[nn];
int pos[nn];
void Init() {
t = sqrt(n);
for(int i = 1 ; i <= t ; i++)
L[i] = (i - 1) * t + 1,
R[i] = i * t;
if(R[t] < n) {
++t;
L[t] = R[t - 1] + 1 , R[t] = n;
}
for(int i = 1 ; i <= t ; i++)
for(int j = L[i] ; j <= R[i] ; j++)
pos[j] = i , sum[i] += a[j];
}
void change(int l , int r , ll dat) {
int p = pos[l] , q = pos[r];
if(p == q) {
for(int i = l ; i <= r ; i++)
a[i] += dat;
sum[p] += dat * (r - l + 1);
return;
}
for(int i = l ; i <= R[p] ; i++)
a[i] += dat;
sum[p] += (R[p] - l + 1) * dat;
for(int i = p + 1 ; i <= q - 1 ; i++)
add[i] += dat;
for(int i = L[q] ; i <= r ; i++)
a[i] += dat;
sum[q] += (r - L[q] + 1) * dat;
}
ll query(int l , int r) {
int p = pos[l] , q = pos[r];
ll ans = 0;
if(p == q) {
for(int i = l ; i <= r ; i++)
ans += a[i];
ans += add[p] * (r - l + 1);
return ans;
}
for(int i = l ; i <= R[p] ; i++)
ans += a[i];
ans += add[p] * (R[p] - l + 1);
for(int i = p + 1 ; i <= q - 1 ; i++)
ans += sum[i] + add[i] * (R[i] - L[i] + 1);
for(int i = L[q] ; i <= r ; i++)
ans += a[i];
ans += add[q] * (r - L[q] + 1);
return ans;
}
int main() {
n = read(); m = read();
for(int i = 1 ; i <= n ; i++)
a[i] = read();
Init();
for(int i = 1 ; i <= m ; i++) {
int l , r , dat;
if(read() == 1) {
l = read(), r = read(), dat = read();
change(l , r , dat);
}
else {
l = read(), r = read();
printf("%lld\n" , query(l , r));
}
}
return 0;
}