分块

分块

分块其实是一种思想,而不是一种数据结构。

分块的基本思想是,通过对数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。

分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相对应的时间复杂度。一般来说,我们取块数为 \(\sqrt{n}\) ,这样在最坏情况下,我们需要处理接近 \(\sqrt{n}\) 个整块,还要对长度为 \(\frac{2n}{\sqrt{n}} = 2\sqrt{n}\) 的零散块单独处理,总时间复杂度为 \(O(\sqrt{n})\)。这是一种根号算法。

分块是一种很灵活的理想,相较于树状数组与线段树,分块的优点是通用性更好,可以维护更多树状数组与线段树无法维护的信息。与线段树不同,块状数组维护的信息不要求满足结合律,也不需要一层一层传递标记。

预处理

具体地使用块状数组,我们先划定出每个块占据的范围

int len = sqrt(n);
for (int i = 1; i <= len; ++i) {
	st[i] = n / len * (i - 1) + 1;
	ed[i] = n / len * i;
}

但是,数组的当都并不一定是一个完全平方数,所以这样下来可能漏下一块,我们要把它们纳入最后一块中

ed[len] = n;

然后,我们为每个元素确定它所归属的块:

for (int i = 1; i <= len; ++i)
    	for (int j = st[i]; j <= ed[i]; ++j)
            bel[j] = i;

最后,如果有必要,我们再预处理每一个块的大小

for (int i = 1; i <= len; ++i)
	size[i] = ed[i] - st[i] + 1;

题目及具体用法

[P3372 【模板】线段树 1](P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

数据只有 \(10^{5}\) ,可以用分块。同时我们需要一个 sum 数组来记录每一块的和,mark 数组来做标记

读入和预处理数据
for (int i = 1; i <= n; ++i)
	A[i] = read();
for (int i = 1; i <= len; ++i)
	for (int j = st[i]; j <= ed[i]; ++j)
		sum[i] += A[j];
区间修改

首先是区间修改,当 x 与 y 在同一块中的时候,直接暴力修改原数组和 sum 数组:

if (bel[x] == bel[y]) {
	for (int i = x; i <= y; ++i) {
		A[i] += k;
		sum[bel[i]] += k;
	}
}

否则,先修改左右两边的零散区间:

for (int i = x; i <= en[bel[x]]; ++i) {
	A[i] += k;
	sum[bel[i]] += k;
}
for (int i = st[bel[y]]; i <= y; ++i) {
	A[i] += k;
	sum[bel[i]] += k;
}

然后对中间的整块打上标记:

for (int i = ble[x] + 1; i < bel[y]; ++i)
	mark[i] += k;
区间查询

同样地,如果左右两边在同一块,直接暴力求和:

if (bel[x] == bel[y]) {
	for (int i = x; i <= y; ++i)
		s += A[i] + mark[bel[i]];
}

否则,暴力计算零散块:

for (int i = x; i <= ed[bel[x]]; ++i)
	s += A[i] + mark[bel[i]]
for (int i = st[bel[y]]; i <= y; ++i)
    s += A[i] + mark[bel[i]];

再处理整块:

for (int i = ble[x] + 1; i < bel[y]; ++i)
    s += sum[i] + mark[i] * size[i];		
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 1e5 + 5;
int n, m, len, st[N], ed[N], siz[N], bel[N];
ll mark[N], sum[N], A[N];

inline int read() {
	int x = 0, f = 1;
	char c = getchar();
	while (!isdigit(c)) {
		if (c == '-') f = -1;
		c = getchar();
 	}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
	return x * f;
}

inline void add(int l, int r, int k) {
	if (bel[l] == bel[r]) {
		for (int i = l; i <= r; ++i) {
			A[i] += k;
			sum[bel[i]] += k;
		}
	} else {
		for (int i = l; i <= ed[bel[l]]; ++i) {
			A[i] += k;
			sum[bel[i]] += k;
		}
		for (int i = st[bel[r]]; i <= r; ++i) {
			A[i] += k;
			sum[bel[i]] += k;
		}
		for (int i = bel[l] + 1; i < bel[r]; ++i) mark[i] += k;
	}
}

inline ll query(int l, int r) {
	ll ans = 0;
	if (bel[l] == bel[r]) {
		for (int i = l; i <= r; ++i) ans = ans + A[i] + mark[bel[i]];
		return ans;
	} else {
		for (int i = l; i <= ed[bel[l]]; ++i) ans += A[i] + mark[bel[i]];
		for (int i = st[bel[r]]; i <= r; ++i) ans += A[i] + mark[bel[i]];
		for (int i = bel[l] + 1; i < bel[r]; ++i) ans += sum[i] + mark[i] * siz[i];
		return ans;
	}
}

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++i) A[i] = read();

	len = sqrt(n);
	for (int i = 1; i <= len; ++i) {
		st[i] = n / len * (i - 1) + 1;
		ed[i] = n / len * i;
	}
	ed[len] = n;
	for (int i = 1; i <= len; ++i) siz[i] = ed[i] - st[i] + 1;
	for (int i = 1; i <= len; ++i)
    	for (int j = st[i]; j <= ed[i]; ++j)
            bel[j] = i;
	for (int i = 1; i <= len; ++i)
		for (int j = st[i]; j <= ed[i]; ++j)
			sum[i] += A[j];
	int opt, x, y, k;
	while (m--) {
		opt = read();
		x = read(), y = read();
		if (opt == 1) {
			k = read();
			add(x, y, k);
		} else printf("%lld\n", query(x, y));
	}
	system("pause");
	return 0;
}
[P2801 教主的魔法](P2801 教主的魔法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

零散块处理起来要容易些,但是我们如何对整块整体处理呢?实际上我们可以维护整块排序后的数组,然后对整块进行询问时直接二分查找即可。

注意,我们每次对零散块进行单独修改的时候,都需要重新排序更新后的数组,这听起来很暴力,但是每个块相对较少,也可以接受,总时间复杂度为 \(O(q \sqrt{n}\log \sqrt{n})\)

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 5;
int n, m, len, bel[N], siz[N], st[N], ed[N], A[N], mark[N];
vector <int> v[N];

inline int read() {
	int x = 0, f = 1;
	char c = getchar();
	while (!isdigit(c)) {
		if (c == '-') f = -1;
		c = getchar();
	}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
	return x * f;
}

inline void block_init() {
	len = sqrt(n);
	for (int i = 1; i <= len; ++i) {
		st[i] = n / len * (i - 1) + 1;
		ed[i] = n / len * i;
	}
	ed[len] = n;

	for (int i = 1; i <= len; ++i) {
		for (int j = st[i]; j <= ed[i]; ++j) {
			bel[j] = i;
			v[i].push_back(A[j]);
		}
		sort(v[i].begin(), v[i].end());
	}
	
	for (int i = 1; i <= len; ++i) siz[i] = ed[i] - st[i] + 1;
}

inline void add(int l, int r, int k) {
	if (bel[l] == bel[r]) {
		for (int i = l; i <= r; ++i) A[i] += k;
		for (int i = 0; i <= v[bel[l]].size(); ++i) v[bel[l]][i] = A[st[bel[l]] + i];
		sort(v[bel[l]].begin(), v[bel[l]].end());
	} else {
		for (int i = l; i <= ed[bel[l]]; ++i) A[i] += k;
		for (int i = st[bel[r]]; i <= r; ++i) A[i] += k;

		for (int i = 0; i <= v[bel[l]].size(); ++i) v[bel[l]][i] = A[st[bel[l]] + i];
		sort(v[bel[l]].begin(), v[bel[l]].end());
		for (int i = 0; i <= v[bel[r]].size(); ++i) v[bel[r]][i] = A[st[bel[r]] + i];
		sort(v[bel[r]].begin(), v[bel[r]].end());

		for (int i = bel[l] + 1; i < bel[r]; ++i) mark[i] += k;
	}
}

inline int query(int l, int r, int k) {
	int ans = 0;
	if (bel[l] == bel[r]) {
		for (int i = l; i <= r; ++i) {
			if (A[i] + mark[bel[i]] >= k) ++ans;
		}
		return ans;
	} else {
		for (int i = l; i <= ed[bel[l]]; ++i) if (A[i] + mark[bel[i]] >= k) ++ans;
		for (int i = st[bel[r]]; i <= r; ++i) if (A[i] + mark[bel[i]] >= k) ++ans;
		for (int i = bel[l] + 1; i < bel[r]; ++i) {
			ans += v[i].end() - lower_bound(v[i].begin(), v[i].end(), k - mark[i]);
		}
		return ans;
	}
}

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; ++i) A[i] = read();

	block_init();

	char opt;
	int x, y, k;
	while (m--) {
		cin >> opt >> x >> y >> k;
		//scanf("%c%d%d%d", &opt, &x, &y, &k);
		if (opt == 'M') {
			add(x, y, k);
		} else {
			printf("%d\n", query(x, y, k));
		}
	}
	//system("pause");
	return 0;
}

分块入门模板题单

[入门题单](题库 - LibreOJ (loj.ac))

原文链接: https://www.cnblogs.com/Miraclys/p/17053659.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    分块

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/312176

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 下午12:18
下一篇 2023年2月16日 下午12:18

相关推荐