算法学习笔记(2):分块

分块

理解

优雅暴力, 对序列进行划分, 进行一些预处理, 总而减少时间复杂度, 有技术含量的是通过列式子, 再通过数学方法或者其他奇奇怪怪的方法, 求出合适的块长。

直接上题目

教主的魔法

题解

块长定为 n , 针对每一块内部进行排序。 对于修改操作, 整块就直接维护一个增量标记, 碎块就暴力一个一个加上后再内部排个序, 反正块长最多 n , 对于查询操作, 每个块二分一下找到第一个大于 C 的位置, 就可以直接查到数量, 剩下的碎块就暴力一个一个比。 这样就完美结束了。

时间复杂度

O(nnlogn)

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6 + 10;
int n, q, raw[N], tmp[N];
namespace Block{
	int block, L[N], R[N], tot, add[N], belong[N];
	void build() {
		memcpy(tmp, raw, sizeof(raw));
		tot = block = sqrt(n);
		for (int i = 1; i <= tot; i++) {
			L[i] = (i - 1) * block + 1;
			R[i] = i * block;
		}
		if (R[tot] < n) tot++, L[tot] = R[tot - 1] + 1, R[tot] = n;
		for (int i = 1; i <= tot; i++) 
			sort(tmp + L[i], tmp + R[i] + 1);
		for (int i = 1; i <= tot; i++) 
			for (int j = L[i]; j <= R[i]; j++) belong[j] = i;
	}
	inline void update(int l, int r, int x) {
		for (int i = l; i <= r; i++) raw[i] += x;
		int pos = belong[l];
		for (int i = L[pos]; i <= R[pos]; i++) tmp[i] = raw[i];
		sort(tmp + L[pos], tmp + R[pos] + 1); 
	}
	void modify(int l, int r, int x) {
		int p = belong[l], q = belong[r];
		if (p == q) update(l, r, x);
		else {
			for (int i = p + 1; i <= q - 1; i++) add[i] += x;
			update(l, R[p], x); update(L[q], r, x);
		}
	}
	int query(int l, int r, int x) {
		int p = belong[l], q = belong[r], res = 0;
		if (p == q) 
			for (int i = l; i <= r; i++) raw[i] + add[p] >= x ? res++ : res = res;
		else {
			for (int i = p + 1; i <= q - 1; i++) {
				int pos = lower_bound(tmp + L[i], tmp + R[i] + 1, x - add[i]) - tmp - 1;
				res += R[i] - pos;
			}
			for (int i = l; i <= R[p]; i++) 
				if (raw[i] + add[p] >= x) res++;
			for (int i = L[q]; i <= r; i++)
				if (raw[i] + add[q] >= x) res++;
		}
		return res;
	}
}
using namespace Block;
int main() {
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; i++) scanf("%d", &raw[i]);
	build();
	char st[5];
	for (int i = 1; i <= q; i++) {
		int l, r, x;
		scanf("%s%d%d%d", st, &l, &r, &x);
		switch(*st) {
			case 'M': modify(l, r, x); break;
			case 'A': printf("%d\n", query(l, r, x)); break;
		}
	}
	return 0;
}

蒲公英

题解

考虑只有查询操作, 并且只需要查询 (l,r) 中的众数, 若两个数出现次数一样, 则取编号小的那一个。 所以维护三个东西。

  1. p[i][j] 表示第 i 块到第 j 块之间的答案。
  2. pre[i][j] 表示前 i 块中编号为 j 的数的数量。
  3. mcnt[i][j] 表示第 i 块到第 j 块之间的答案的编号。 (用来解决出现次数一样取编号较小的数)

这三个数组都提前预处理, 预处理没什么难度, 朴素的 for 循环。

那么对于每一次查询 (l,r), 对于整块就直接取 p[i][j]O(1) 解决, 碎块里的每一个数都有可能被选为答案, 所以都要考虑到, 就开一个临时数组取出 pre[j][]pre[i1][], 再在临时数组基础上for 循环遍历碎块中的每一个数, 找出答案。 这样就完美解决了。

时间复杂度

O(nn)

代码 (附debug)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 4e4 + 10; 
const int NN = 2e2 + 10;
int n, m, raw[N], val[N], cnt;
void disc() {
	memcpy(val, raw, sizeof(raw));
	sort(raw + 1, raw + 1 + n);
	cnt = unique(raw + 1, raw + 1 + n) - raw - 1;
	for (int i = 1; i <= n; i++) {
		int pos = lower_bound(raw + 1, raw + 1 + cnt, val[i]) - raw;
		val[i] = pos;
	}
//	for (int i = 1; i <= n; i++) cout << val[i] << " "; 
}
namespace Block{
	int tot, block, pre[NN][N], pp[NN][NN], L[NN], R[NN], mcnt[NN][NN], belong[N];
	void build() {
		block = tot = sqrt(n);
		for (int i = 1; i <= tot; i++) {
			L[i] = (i - 1) * block + 1;
			R[i] = i * block;
		}
		if (R[tot] < n) tot++, L[tot] = R[tot - 1] + 1, R[tot] = n;
		for (int i = 1; i <= tot; i++) 
			for (int j = L[i]; j <= R[i]; j++) 
				belong[j] = i;
		
		for (int i = 1; i <= tot; i++) {
			int tmp[N] = {0};
			for (int j = i; j <= tot; j++) {
				pp[i][j] = pp[i][j - 1],
				mcnt[i][j] = mcnt[i][j - 1];
				for (int k = L[j]; k <= R[j]; k++) {
					tmp[val[k]]++;
					if (mcnt[i][j] < tmp[val[k]]) pp[i][j] = val[k], mcnt[i][j] = tmp[val[k]];
					else if (mcnt[i][j] == tmp[val[k]] && pp[i][j] > val[k]) pp[i][j] = val[k];
				}
			} 
		}
		for (int i = 1; i <= tot; i++) {
			for (int j = 1; j <= cnt; j++) 
				pre[i][j] = pre[i - 1][j];		
			for (int j = L[i]; j <= R[i]; j++) 
				pre[i][val[j]]++;
		}
	}
	int tmp[N], _maxs, _mcnt;
	void update(int l, int r) {
		for (int i = l; i <= r; i++) {
			tmp[val[i]]++;
			if (_mcnt < tmp[val[i]]) _maxs = val[i], _mcnt = tmp[val[i]];
			else if (_mcnt == tmp[val[i]] && _maxs > val[i]) _maxs = val[i]; 
		}
	}
	int query(int l, int r) {	
		int p = belong[l], q = belong[r];
		_maxs = _mcnt = 0;
//		for (int i = 1; i <= cnt; i++) tmp[i] = pre[q - 1][i] - pre[p][i]; 
		if (p == q) {
			for (int i = l; i <= r; i++) tmp[val[i]] = 0;
			update(l, r);
		}
		else {
//			for (int i = p + 1; i <= q - 1; i++) 
//				if (_mcnt < mcnt[i]) _maxs = maxs[i], _mcnt = mcnt[i];
//				else if (_mcnt == mcnt[i] && _maxs > maxs[i]) _maxs = maxs[i]; 
			_maxs = pp[p + 1][q - 1], _mcnt = mcnt[p + 1][q - 1];
			for (int i = l; i <= R[p]; i++) tmp[val[i]] = pre[q - 1][val[i]] - pre[p][val[i]];
			for (int i = L[q]; i <= r; i++) tmp[val[i]] = pre[q - 1][val[i]] - pre[p][val[i]];
			update(l, R[p]); update(L[q], r);
		}
		return _maxs;
	}
}
using namespace Block;
int main() {
//	freopen("P4168_1.in", "r", stdin);
//	freopen("1.out", "w", stdout); 
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &raw[i]);
	disc(); build();
	int pre = 0;
	for (int i = 1; i <= m; i++) {
		int l, r; scanf("%d%d", &l, &r);
		l = (l + pre - 1) % n + 1, r = (r + pre - 1) % n + 1;
		if (l > r) swap(l, r);
		pre = raw[query(l, r)];
		printf("%d\n", pre);
	}
	return 0;
}
/*
8 3
1 7 10 46 6 1 7 1
1 5
2 6
5 7
*/

由乃救爷爷

题解

纯纯板子题, 但是数据范围 1e7, 显然普通分块 O(nn) 过不了, 但是考虑到维护区间最大值, 还不带修改, 所以可以套一个st表。

对于每一个查询 (l,r), 整块的答案维护一个 f[i][j] 表示第 i 块到 i+(1<<j)1 块的最大值, 注意 i 表示的是第 i 块而不是第 i 个。st表预处理 O(nlogn), 询问则是 O(1)。 有关碎块的答案就维护 pre[i][j]suf[i][j] 表示第 i 块内的前缀最大值和后缀最大值, 这样预处理是 O(n), 询问是 O(1)

时间复杂度

O(n) 带点常数

代码

警钟长鸣:
st表的 len[] 数组需要将第0位赋值为-1, 不然 len[1] 就是 1, len[2] 就是 2, 很明显不对, len[i]数组的定义为小于等于 i 的最大2的次幂, 你也不能从第二位开始计算, 这样在查询时特判那一步又会出问题。
就是这里if (k != -1) res = max(f[k][p + 1], f[k][q - (1 << k)]);
你若判 k != 0 就会出锅, 因为len[1] 也等于0, 这样的话k==1 就没算到。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n, m, s;
namespace GenHelper
{
    unsigned z1,z2,z3,z4,b;
    unsigned rand_()
    {
    b=((z1<<6)^z1)>>13;
    z1=((z1&4294967294U)<<18)^b;
    b=((z2<<2)^z2)>>27;
    z2=((z2&4294967288U)<<2)^b;
    b=((z3<<13)^z3)>>21;
    z3=((z3&4294967280U)<<7)^b;
    b=((z4<<3)^z4)>>12;
    z4=((z4&4294967168U)<<13)^b;
    return (z1^z2^z3^z4);
    }
}
void srand(unsigned x)
{using namespace GenHelper;
z1=x; z2=(~x)^0x233333333U; z3=x^0x1234598766U; z4=(~x)+51;}
int read()
{
    using namespace GenHelper;
    int a=rand_()&32767;
    int b=rand_()&32767;
    return a*32768+b;
}
const int N = 2e7 + 10;
const int NN = 5e3;
const int MB = 15;
namespace Block{
	int raw[N], L[NN], R[NN], block, tot, f[MB][NN], pre[NN][NN], suf[NN][NN], belong[N], len[NN] = {-1};
	void build() {
		tot = block = sqrt(n);
		for (int i = 1; i <= tot; i++) {
			L[i] = (i - 1) * block + 1;
			R[i] = i * block;
		}
		if (R[tot] < n) tot++, L[tot] = R[tot - 1] + 1, R[tot] = n;
		for (int i = 1; i <= tot; i++) len[i] = len[i / 2] + 1;
		for (int i = 1; i <= tot; i++) 
			for (int j = L[i]; j <= R[i]; j++) belong[j] = i;
		for (int i = 1; i <= tot; i++) {
			for (int j = L[i]; j <= R[i]; j++) pre[i][j - L[i] + 1] = max(pre[i][j - L[i]], raw[j]); 
			for (int j = R[i]; j >= L[i]; j--) suf[i][R[i] - j + 1] = max(suf[i][R[i] - j], raw[j]);
			f[0][i] = suf[i][R[i] - L[i] + 1]; 
		}
		for (int j = 1; j <= 13; j++) 
			for (int i = 1; i + (1 << (j - 1)) - 1 <= tot; i++) 
				f[j][i] = max(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
		
	}
	int query(int l, int r) {
		int p = belong[l], q = belong[r], res = 0;
		if (p == q) 
			for (int i = l; i <= r; i++) res = max(res, raw[i]);
		else {
			int k = len[q - 1 - p];
			if (k != -1) res = max(f[k][p + 1], f[k][q - (1 << k)]);
			res = max(res, suf[p][R[p] - l + 1]);
			res = max(res, pre[q][r - L[q] + 1]);
		}
		return res;
	}
}
using namespace Block;
unsigned long long ans;
int main() {
	scanf("%d%d%d", &n, &m, &s); srand(s);
	for (int i = 1; i <= n; i++) raw[i] = read();
	build(); 
	for (int i = 1; i <= m; i++) {
		int l = read() % n + 1, r = read() % n + 1;
		if (l > r) swap(l, r);
		ans += query(l, r);
	}
	printf("%llu", ans);
	return 0;
}

愉快结束

posted @   qqrj  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示