luo3372线段树模板的分块做法

题目大意

请你维护一个有n个元素的整数序列,要求支持区间查询&区间修改

对于100%的数据,(1<=n<=10^5)

分析

正常做法是线段树维护区间修改、区间查询,今天我要讲的是一种暴力做法:分块

分块的思想并不复杂,分块把一个长度为n的区间分成num段,操作时如果是整段用标记修改,不是整段的部分暴力修改

分析时间复杂度:在这题中,每段的标记修改是(O(1))的,最多有num段,整段标记修改所用时间是(O(num))的;不是整段的部分最多有(O(n/num))个,暴力修改所用的时间是(O(n/num))的;所以总时间是(O(num+n/num))

根据基本不等式,num取(sqrt n)时该式有最小值;所以num取(sqrt n)

实现

分块的思想并不复杂,时间复杂度也不玄学,但是实现起来并不方便(可能是我弱)

分块的修改/查询都分为2部分:

  1. 整块的修改
  2. “零头”的修改

整块的修改是否简便:add[i] += val;
“零头”的修改直接修改,同时不要忘了维护所在块的信息:

a[i] += val;
sum[id[i]] += val;

修改就愉快地解决了,查询和修改差不多。

完整代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn = 100007;
int n, m, num, id[maxn];
long long sum[1000], add[1000], a[maxn];

int main(){
	scanf("%d%d", &n, &m);
	num = sqrt(n);
	for (int i = 1; i <= n; ++i){
		scanf("%lld", &a[i]);
		id[i] = (i-1) / num;
		sum[id[i]] += a[i];
	}

	while (m--){
		int d; scanf("%d", &d);
		if (d==1){
			int x, y, C; scanf("%d%d%d", &x, &y, &C);
			int Leftid = (x-1) / num + 1;
			int Rightid = (y-1) / num - 1;

			long long res = 0;
			for (int i = Leftid; i <= Rightid; ++i)
				 add[i] += C;

			for (int i = x; i <= Leftid * num; ++i)
				a[i] += C, sum[id[i]] += C;

			for (int i = (Rightid+1)*num+1; i <= y; ++i)
				a[i] += C, sum[id[i]] += C;
		}else{
			int x, y; scanf("%d%d", &x, &y);
			int Leftid = (x-1) / num + 1;
			int Rightid = (y-1) / num - 1;

			if (id[x] == id[y]){
				long long res = 0;
				for (int i = x; i <= y; ++i)
					res += a[i] + add[id[i]];
				printf("%lld
", res);
				continue;
			}

			long long res = 0;
			for (int i = Leftid; i <= Rightid; ++i)
				res += sum[i] + add[i] * num;

			for (int i = x; i <= Leftid * num; ++i)
				res += a[i] + add[Leftid-1];

			for (int i = (Rightid+1)*num+1; i <= y; ++i)
				res += a[i] + add[Rightid+1];

			printf("%lld
", res);

		}
	}
	return 0;
}

原文地址:https://www.cnblogs.com/YJZoier/p/9721774.html