[学习笔记]分块

分块简介

分块,其实不是数据结构,是一种思想,但常用于数据结构中,所以通常把分块当作数据结构来看待。

分块的主要思想就是将一段数列分为 \(\sqrt{n}\) 块,进行修改和查询操作,有着 \(O(n \sqrt{n})\) 的时间复杂度。虽然效率不如线段树和树状数组的 \(O(n \log{n})\),但比线段数更好调,也是它的一大优点。

算法简述

一开始先预处理出每个点所在块,一些可以自行加,比如某个块的左端点和右端点。

先讲修改操作。

修改操作

以长度为 \(9\) 的序列为例子,比如我们现在将 \(\left [ 2, 7\right ]\) 增加 \(1\),我们可以将 \(\left [ 2, 7\right ]\) 拆分成 \(\left [ 2, 3\right ]\)\(\left [ 4, 6\right ]\)\(\left [ 7, 7\right ]\) 进行修改。

发现了吗?中间那个区间的长度刚好是 \(\sqrt{9}\) !事实上,中间那个区间长度就是\(\sqrt{\text{整个序列长度}}\) 的倍数。另外两个区间的长度不会超过 \(\sqrt{\text{整个序列长度}}\)

于是,我们对于中间的区间,一个一个块修改,另外两个区间一个一个点修改。

void update(int l, int r, int k)
{
	int lid = id[l], rid = id[r];
	for (int i = l; id[i] == lid && i <= r; i++)a[i] += k;
	for (int i = lid + 1; i < rid; i++)lazy[i] += k;//块的懒标记修改
	if (lid != rid)for (int i = r; id[i] == rid; i--)a[i] += k;
}

查询操作

与上面一个道理,用一个数组表示块的和,就不给参考的代码了,要注意查询点的时候有没有把懒标记加上去。

例题

例题 1 Lougu P3368 【模板】树状数组 2

P3368 【模板】树状数组 2

本来想放 P3372 【模板】线段树 1 的,但是 \(1 \le N \le 10 ^ 5\) 水不掉,于是放了这个,其实道理都一样。

这么水就不放代码了吧。。。

例题2 Luogu P3870 [TJOI2009] 开关

P3870 [TJOI2009] 开关

同样很水,修改操作就是懒标记和点 \(\operatorname{xor} 1\),然后求和。

那么也不放代码了吧 qwq。

例题3 Luogu P2801 教主的魔法

P2801 教主的魔法

分块开始有意思了。

修改操作就不多说了,重点讲一下如何求大于等于 \(C\) 的人数。

用一个数组维护当前块所有人的身高,并且每个块都是单调不递减的,用于查询时进行二分。查询时仍然是先查询左右两边的散块,中间的用 lower_bound 或者手写二分求值。

#include <bits/stdc++.h>
// #pragma GCC optimize("Ofast,unroll-loops")
using namespace std;
const int N = 1e6 + 5;
int a[N], id[N], lazy[N], t[N], n, len;
void Sort(int k)
{
	int l = (k - 1) * len + 1, r = min(k * len, n);
	for (int i = l; i <= r; i++)t[i] = a[i];
	sort(t + l, t + r + 1);
}
void update(int l, int r, int k)
{
	int lid = id[l], rid = id[r];
	for (int i = l; id[i] == lid && i <= r; i++)a[i] += k;
	for (int i = lid + 1; i < rid; i++)lazy[i] += k;
	if (lid != rid)for (int i = r; id[i] == rid; i--)a[i] += k;
	Sort(lid); Sort(rid);
}
int query(int l, int r, int k)
{
	int lid = id[l], rid = id[r], ans = 0;
	for (int i = l; id[i] == lid && i <= r; i++)if (a[i] + lazy[lid] >= k)ans++;
	for (int i = lid + 1; i < rid; i++)
		ans += i * len 
			- (lower_bound(t + (i - 1) * len + 1, t + i * len + 1, k - lazy[i]) - t) + 1;
	if (lid != rid)for (int i = r; id[i] == rid; i--)if (a[i] + lazy[rid] >= k)ans++;
	return ans;
}
int main()
{
	int T, l, r, k;
	char opt;
	cin >> n >> T;
	len = sqrt(n);
	memset(t, 0x3f3f3f3f, sizeof(t));
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int i = 1; i <= n; i++)id[i] = (i - 1) / len + 1;
	for (int i = 1; i <= n; i++)t[i] = a[i];
	for (int i = 1; i <= int(ceil(n / len)); i++)sort(t + (i - 1) * len + 1, t + i * len + 1);
	while (T--)
	{
		cin >> opt >> l >> r >> k;
		if (opt == 'M')update(l, r, k);
		else cout << query(l, r, k) << endl;
	}
	return 0;
}

例题4 P4168 [Violet] 蒲公英

P4168 [Violet] 蒲公英

十分有意思的一道题。

如果不强制在线的话,就有莫队的一个做法,但是强制在线把莫队卡掉了,那怎么办?

首先,区间众数没有可加性,比如左边区间的众数是 \(1\),右边的也是 \(1\),两个区间合在一起就不一定是 \(1\)

此时开发人类智慧,我们知道,答案 \(ans \in \text{中间整块}\cup\text{两边散块}\)

\(f_{l, r}\) 为第 \(l\) 块到第 \(r\) 块的众数,\(s_{i, j}\) 为第 \(1\) 块到第 \(i\)\(j\) 的出现次数。

先预处理出 \(f\)\(s\) 数组,设左端点所属块编号为 \(lid\),右属块编号为 \(rid\)\(ans\) 初值为 \(f_{lid, rid}\),遍历两边散块,如果散块中的出现次数比答案出现次数大,更改答案。

/*Code By Manipula*/
#include <bits/stdc++.h>
// #define Fileopen
// #pragma GCC optimize(2)
#define INF 0x3f3f3f3f
#define int long long
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
#define mst(arr, num) memset(arr, num, sizeof(arr))
#define max(x, y)((x) > (y) ? (x) : (y))
#define min(x, y)((x) < (y) ? (x) : (y))
#define swap(x, y) x ^= y ^= x ^= y
#define sqrt(n) pow(n, 0.5)
#define lowbit(x) (x & -x)
#define sort stable_sort
#define endl '\n'
using namespace std;
const int N = 4e4 + 5;
const int M = 205;
int f[M][M], s[M][N], l[M], r[M], id[N], a[N], b[N], B[N];
int n, T, tot, block, len, lstAns;
int query(int L, int R)
{
	mst(B, 0); int lid = id[L], rid = id[R], ans = 0;
	if (rid - lid <= 1)
	{
		FOR(i, L, R)
		{
			if (++B[a[i]] > B[ans])ans = a[i];
			else if (B[a[i]] == B[ans] && a[i] < ans)ans = a[i];
		}
	}
	else
	{
		ans = f[lid + 1][rid - 1];
		FOR(i, L, r[lid])B[a[i]]++;
		FOR(i, l[rid], R)B[a[i]]++;
		FOR(i, L, r[lid])
		{
			int _A = B[ans] + s[rid - 1][ans] - s[lid][ans], 
				_B = B[a[i]] + s[rid - 1][a[i]] - s[lid][a[i]];
			if (_A < _B)ans = a[i];
			else if (_A == _B && a[i] < ans)ans = a[i];
		}
		FOR(i, l[rid], R)
		{
			int _A = B[ans] + s[rid - 1][ans] - s[lid][ans], 
				_B = B[a[i]] + s[rid - 1][a[i]] - s[lid][a[i]];
			if (_A < _B)ans = a[i];
			else if (_A == _B && a[i] < ans)ans = a[i];
		}
	}
	return ans;
}
signed main()
{
	#ifdef Fileopen
		freopen(".in", "r", stdin);
		freopen(".out", "w", stdout);
	#endif
	cin >> n >> T; block = sqrt(n), len = ceil(1.0 * n / block);
	FOR(i, 1, n)id[i] = (i - 1) / block + 1;
	FOR(i, 1, len)l[i] = (i - 1) * block + 1;
	FOR(i, 1, len)r[i] = i * block;
	r[len] = len * block;
	FOR(i, 1, n)cin >> a[i];
	FOR(i, 1, n)b[i] = a[i];
	sort(b + 1, b + n + 1); tot = unique(b + 1, b + n + 1) - b - 1;
	FOR(i, 1, n)a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b; 
	FOR(i, 1, len)
	{
		mst(B, 0); int mode = 0;
		FOR(j, i, len)
		{
			FOR(k, l[j], r[j])
				if (++B[a[k]] > B[mode])mode = a[k];
				else if (B[a[k]] == B[mode] && a[k] < mode)mode = a[k];
			f[i][j] = mode;
		}
	}
	FOR(i, 1, len)
	{
		FOR(j, 1, tot)s[i][j] = s[i - 1][j];
		FOR(j, l[i], r[i])s[i][a[j]]++;
	}
	while (T--)
	{
		int L, R; cin >> L >> R;
		L = (L + lstAns - 1) % n + 1, R = (R + lstAns - 1) % n + 1;
		if (L > R)swap(L, R);
		cout << (lstAns = b[query(L, R)]) << endl;
	}
	return 0;
}

习题

Luogu P4135 作诗

posted @ 2024-01-28 00:59  Manipula  阅读(64)  评论(0)    收藏  举报