分块

分  块

模板题

传送门

没有专门模板,就直接用线段树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(变量与题目不同)

  1. 若l,r同属一个分块,直接暴力,将a[l~r]加上dat

  2. 否则,设l处于第p个分块,r属于第q个分块,

    1. \(对于i\in[p+1,q+1],add_i+=dat\)

    2. 对于开头,结尾不足一整段的两部分,直接用朴素方法更新即可

看代码:

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;
} 
posted @ 2020-11-25 16:43  追梦人1024  阅读(124)  评论(0编辑  收藏  举报