LOJ数列分块1-9全家桶

╄→гoц情女王★ 提交于 2020-09-30 16:17:30

背景

终于全艹过去了……累死我了……

首先感谢 RP 大佬,Martin 神犇,涛队及 sh 妹的帮助

正题

  • 数列分块1

    这一题比较简单,无脑分块即可。

#include <cstdio>
#include <cmath>
#include <algorithm>

const int N = 50000;
int a[N + 5], n, Len;
int Pos[N + 5], Add[N + 5];

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &a[i]);
		Pos[i] = (i - 1) / Len + 1;
	
	} for(int i = 1; i <= n; i ++) {
		int opt, l, r, c;
		scanf("%d%d%d%d", &opt, &l, &r, &c);
		if(!opt) {
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) a[j] += c;
			if(Pos[l] != Pos[r]) 
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) a[j] += c;
			for(int j = Pos[l] + 1; j < Pos[r]; j ++) Add[j] += c;
		
		} else printf("%d\n", a[r] + Add[Pos[r]]);
	} return 0;
}
  • 数列分块2

    首先分块,对每一个块用 vector 存下并排序。

    查询:散块暴力,整块二分。

    修改:散块暴力重构 vector,整块打标记。

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>

const int N = 50000;
int a[N + 5], n;
int Pos[N + 5], Add[N + 5], Len;
std::vector<int> V[N + 5];

void Rebuild(int);

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	for(int i = 1; i <= n; i ++)
		V[Pos[i] = (i - 1) / Len + 1].push_back(a[i]);
	for(int i = 1; i <= Pos[n]; i ++)
		std::sort(V[i].begin(), V[i].end());

	for(int i = 1; i <= n; i ++) {
		int opt, l, r, c;
		scanf("%d%d%d%d", &opt, &l, &r, &c);
		if(!opt) {
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) a[j] += c;
			Rebuild(Pos[l]);
			if(Pos[l] != Pos[r]) {
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) a[j] += c;
				Rebuild(Pos[r]);
			} for(int j = Pos[l] + 1; j < Pos[r]; j ++) Add[j] += c;
		
		} else {
			int Ans = 0; c = c * c;
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) Ans += ((a[j] + Add[Pos[j]]) < c);
			if(Pos[l] != Pos[r])
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) Ans += ((a[j] + Add[Pos[j]]) < c);
			for(int j = Pos[l] + 1; j < Pos[r]; j ++)
				Ans += std::lower_bound(V[j].begin(), V[j].end(), c - Add[j]) - V[j].begin();
			printf("%d\n", Ans);
		}
	} return 0;

} void Rebuild(int p) {
	V[p].clear();
	for(int i = (p - 1) * Len + 1; i <= std::min(p * Len, n); i ++) V[p].push_back(a[i]);
	std::sort(V[p].begin(), V[p].end());
}
  • 数列分块3

    和上题没啥太大区别……改一下答案统计方式就行。

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>

const int N = 100000;
int a[N + 5], n;
int Pos[N + 5], Add[N + 5], Len;
std::vector<int> V[N + 5];

void Rebuild(int);

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	for(int i = 1; i <= n; i ++)
		V[Pos[i] = (i - 1) / Len + 1].push_back(a[i]);
	for(int i = 1; i <= Pos[n]; i ++)
		std::sort(V[i].begin(), V[i].end());

	for(int i = 1; i <= n; i ++) {
		int opt, l, r, c;
		scanf("%d%d%d%d", &opt, &l, &r, &c);
		if(!opt) {
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) a[j] += c;
			Rebuild(Pos[l]);
			if(Pos[l] != Pos[r]) {
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) a[j] += c;
				Rebuild(Pos[r]);
			} for(int j = Pos[l] + 1; j < Pos[r]; j ++) Add[j] += c;
		
		} else {
			bool Flag = false; int Ans;
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++)
				if(a[j] + Add[Pos[j]] < c) { 
					if(!Flag) Ans = a[j] + Add[Pos[j]];
					else Ans = std::max(Ans, a[j] + Add[Pos[j]]);
					Flag = true;
			
			} if(Pos[l] != Pos[r])
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) 
					if(a[j] + Add[Pos[j]] < c) { 
						if(!Flag) Ans = a[j] + Add[Pos[j]];
						else Ans = std::max(Ans, a[j] + Add[Pos[j]]);
						Flag = true;
			
			} for(int j = Pos[l] + 1; j < Pos[r]; j ++) {
				int tap = std::lower_bound(V[j].begin(), V[j].end(), c - Add[j]) - V[j].begin() - 1;
				if(tap < 0) continue;
				if(V[j][tap] + Add[j] < c) {
					if(!Flag) Ans = V[j][tap] + Add[j];
					else Ans = std::max(Ans, V[j][tap] + Add[j]);
					Flag = true;
				}
			} printf("%d\n", Flag? Ans : -1);
		}
	} return 0;

} void Rebuild(int p) {
	V[p].clear();
	for(int i = (p - 1) * Len + 1; i <= std::min(p * Len, n); i ++) V[p].push_back(a[i]);
	std::sort(V[p].begin(), V[p].end());
}
  • 数列分块4

    分块维护\(Add\)标记与\(Sum\)

    修改:散块\(Sum\)暴力加,整块加\(Add\)

    查询:散块暴力,整块\(Sum + Add * Len\)

#include <cstdio>
#include <cmath>
#include <algorithm>

const int N = 50000;
int Pos[N + 5], n, Len;
long long a[N + 5], Sum[N + 5], Add[N + 5];

int L(int);
int R(int);

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		scanf("%lld", &a[i]);
		Pos[i] = (i - 1) / Len + 1, Sum[Pos[i]] += a[i];
	
	} for(int i = 1; i <= n; i ++) {
		int opt, l, r; long long c;
		scanf("%d%d%d%lld", &opt, &l, &r, &c);
		if(!opt) {
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) Sum[Pos[j]] += c, a[j] += c;
			if(Pos[l] != Pos[r]) 
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) Sum[Pos[j]] += c, a[j] += c;
			for(int j = Pos[l] + 1; j < Pos[r]; j ++) Add[j] += c;

		} else {
			long long Ans = 0;
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) Ans = (Ans + a[j] + Add[Pos[j]]) % (c + 1);
			if(Pos[l] != Pos[r]) 
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) Ans = (Ans + a[j] + Add[Pos[j]]) % (c + 1);
			for(int j = Pos[l] + 1; j < Pos[r]; j ++) Ans = (Ans + Sum[j] + Add[j] * (R(j) - L(j) + 1)) % (c + 1);
			printf("%lld\n", Ans);
		}
	} return 0;

} int L(int p) {
	return std::max((p - 1) * Len + 1, 1);

} int R(int p) {
	return std::min(p * Len, n);
}
  • 数列分块5

    洛谷上有个类似的题目,《上帝造题的七分钟》。

    发现对于任意数\(x (x <= 2^{31} - 1)\),在进行若干次开方操作后会变成1。并且这个次数在10以内。

    所以我们可以维护每个块中1的个数,若一整块中全为1,可以直接跳过。

    当然, 使用 DSU 或链表皆可。

#include <cstdio>
#include <cmath>
#include <algorithm>

const int N = 50000;
int a[N + 5], n, Len;
int Pos[N + 5], Cnt[N + 5];

int L(int);
int R(int);
void Zoe(int);

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &a[i]);
		Pos[i] = (i - 1) / Len + 1, Cnt[Pos[i]] += (a[i] == 1);
	
	} for(int i = 1; i <= n; i ++) {
		int opt, l, r, c;
		scanf("%d%d%d%d", &opt, &l, &r, &c);
		if(!opt) {
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) Zoe(j);
			if(Pos[l] != Pos[r]) 
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) Zoe(j);
			for(int j = Pos[l] + 1; j < Pos[r]; j ++) 
				if(Cnt[j] != R(j) - L(j) + 1)
					for(int k = L(j); k <= R(j); k ++) Zoe(k);

		} else {
			int Ans = 0;
			for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) Ans += a[j];
			if(Pos[l] != Pos[r]) 
				for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) Ans += a[j];
			for(int j = Pos[l] + 1; j < Pos[r]; j ++)
				if(Cnt[j] == R(j) - L(j) + 1) Ans += Cnt[j];
				else for(int k = L(j); k <= R(j); k ++) Ans += a[k];
			printf("%d\n", Ans);
		}
	} return 0;

} int L(int p) {
	return std::max((p - 1) * Len + 1, 1);

} int R(int p) {
	return std::min(p * Len, n);

} void Zoe(int p) {
	bool Flag = (a[p] > 1);
	a[p] = sqrt(a[p]), Cnt[Pos[p]] += (Flag && (a[p] == 1));
}
  • 数列分块6

    定期重构。

    对于每一个块都开个 vector 来支持插入操作。

    但是,当一个块的大小过大时复杂度难以得到保证。

    这时就需要重新分块。

    1. 按时间重构。 每\(\sqrt n\)次操作过后暴力\(O(n)\)重构。

    2. 当某一块超过设定的阀值时将其分裂。

    查询暴力跳块统计排名即可。

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
#include <map>

const int N = 200000;
int r[N + 5], n, Len;
std::vector<int> V[N / 2 + 5];

int L(int);
int R(int);
void Insert(int, int);
std::pair<int, int> Query(int);
void Rebuild();

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		int tap;
		scanf("%d", &tap);
		V[(i - 1) / Len + 1].push_back(tap);
	
	} for(int i = 1; i <= n; i ++) {
		int opt, l, r, c;
		scanf("%d%d%d%d", &opt, &l, &r, &c);
		if(!opt) Insert(l, r);
		else {
			std::pair<int, int> tap = Query(r);
			printf("%d\n", V[tap.first][tap.second]);
		}
	} return 0;

} int L(int p) {
	return std::max((p - 1) * Len + 1, 1);

} int R(int p) {
	return std::min(p * Len, n);

} void Insert(int Loc, int Val) {
	std::pair<int, int> tap = Query(Loc);
	V[tap.first].insert(V[tap.first].begin() + tap.second, Val);

} std::pair<int, int> Query(int p) {
	int P, Cnt; P = Cnt = 0;
	while(true) {
		if(Cnt + V[++P].size() < p) {
			Cnt += V[P].size(); continue;
		} return std::make_pair(P, p - Cnt - 1);
	}
} void Rebuild() {
	int Cnt = 0, LLen = (n - 1) / Len + 1;
	for(int i = 1; i <= LLen; i ++) { 
		for(int j = 0; j < V[i].size(); j ++) r[++Cnt] = V[i][j];
		V[i].clear();
	
	} Len = sqrt(Cnt);
	for(int i = 1; i <= Cnt; i ++)
		V[(i - 1) / Len + 1].push_back(r[i]);
}
  • 数列分块7

    做过洛谷上的线段树 2 后对这道题当然也不怕。

    每一个块维护两个标记(加法与乘法):\(Add\)\(Mul\)

    乘法优先级高于加法。

    若当前块加法标记为\(a\), 乘法标记为\(m\)

    \(*c\) : \(a * c\)\(b * c\)

    \(+c\) : \(a + c\)

    修改:需要注意的是,散块修改会破坏\(Mul\)标记,需要暴力计算\(Mul\)\(Add\)贡献并清空。

#include <cstdio>
#include <cmath>
#include <algorithm>

const int N = 100000;
const int Mod = 10007;
int Pos[N + 5], n, Len;
long long a[N + 5], Mul[N + 5], Add[N + 5];

int L(int);
int R(int);
void Zoe(int);

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		scanf("%lld", &a[i]);
		Pos[i] = (i - 1) / Len + 1, Mul[Pos[i]] = 1;
	
	} for(int i = 1; i <= n; i ++) {
		int opt, l, r; long long c;
		scanf("%d%d%d%lld", &opt, &l, &r, &c);
		if(opt < 2) {
			Zoe(Pos[l]);
			for(int j = l; j <= std::min(r, R(Pos[l])); j ++) a[j] = (a[j] + ((!opt) ? c : a[j] * (c - 1))) % Mod;
			if(Pos[l] != Pos[r]) { 
				Zoe(Pos[r]);
				for(int j = r; j >= std::max(l, L(Pos[r])); j --) a[j] = (a[j] + ((!opt) ? c : a[j] * (c - 1))) % Mod;
			} for(int j = Pos[l] + 1; j < Pos[r]; j ++) 
				(!opt) ? (Add[j] = (Add[j] + c) % Mod) : (Mul[j] = Mul[j] * c % Mod, Add[j] = Add[j] * c % Mod);

		} else printf("%lld\n", (a[r] * Mul[Pos[r]] + Add[Pos[r]]) % Mod);
	} return 0;

} int L(int p) {
	return std::max((p - 1) * Len + 1, 1);

} int R(int p) {
	return std::min(p * Len, n);

} void Zoe(int p) {
	for(int i = L(p); i <= R(p); i ++)
		a[i] = (a[i] * Mul[p] + Add[p]) % Mod;
	Mul[p] = 1, Add[p] = 0;
}
  • 数列分块8

    借助修改的特殊性:修改一段区间为一样的颜色 c 。

    如果修改到了整块,发现可以直接标记,从而降低时间复杂度。

    修改:散块暴力,并破坏标记。整块标记。

    查询:散块暴力,整块查标记。

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>

const int N = 100000;
const int Sec = -32767;
int a[N + 5], n;
int Pos[N + 5], Col[N + 5], Len;

int L(int);
int R(int);
void Zoe(int);

int main() {
	scanf("%d", &n), Len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &a[i]);
		Pos[i] = (i - 1) / Len + 1, Col[Pos[i]] = Sec;

	} for(int i = 1; i <= n; i ++) {
		int l, r, c, Ans = 0;
		scanf("%d%d%d", &l, &r, &c), Zoe(Pos[l]);
		for(int j = l; j <= std::min(r, Pos[l] * Len); j ++) Ans += (a[j] == c), a[j] = c;
		Col[Pos[l]] = Sec;
		if(Pos[l] != Pos[r]) {
			Zoe(Pos[r]);
			for(int j = r; j >= std::max(l, (Pos[r] - 1) * Len + 1); j --) Ans += (a[j] == c), a[j] = c;
			Col[Pos[r]] = Sec;

		} for(int j = Pos[l] + 1; j < Pos[r]; j ++) {
			if(Col[j] != Sec) { 
				if(Col[j] == c) Ans += R(j) - L(j) + 1;
			} else for(int k = L(j); k <= R(j); k ++) Ans += (a[k] == c);
			Col[j] = c;
		} printf("%d\n", Ans);
	} return 0;

} int L(int p) {
	return std::max((p - 1) * Len + 1, 1);

} int R(int p) {
	return std::min(p * Len, n);

} void Zoe(int p) {
	if(Col[p] == Sec) return;
	for(int i = L(p); i <= R(p); i ++) a[i] = Col[p];
	Col[p] = Sec;
}
  • 数列分块9

    首先,本题无修改操作。

    对于一段区间的询问,答案可能为中间一段连续的整块和两边的散块。

    因为无修改操作,所以可以直接大力预处理每段连续的整块的最小众数。

    然后对于两边散块暴力统计即可。

    下方代码实现中,使用 map 二次映射实现离散化。(懒

    然后时间被卡。 最后通过调整块的大小与涛队点拨,在\(Len = 30\)下通过了此题。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>

const int N = 100000;
int Pos[N + 5], F[5000][5000], n, Len;
int a[N + 5], Re_a[N + 5], r[N + 5], Cnt;
std::vector<int> Vc[N + 5];
std::map<int, int> Ct;

int L(int);
int R(int);
void Init_F();
int Number_Counter(int, int, int); //二分求在[l, r]中的Val个数

int main() {
	scanf("%d", &n), Len = 30;
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &a[i]);
		Pos[i] = (i - 1) / Len + 1;
		if(!Ct[a[i]]) {
			Ct[a[i]] = ++Cnt; Re_a[Cnt] = a[i]; //Re_a为原数组
		} Vc[a[i] = Ct[a[i]]].push_back(i); //记录每个a[i]的出现位置
	} Init_F();
	for(int i = 1; i <= n; i ++) {
		int l, r;
		scanf("%d%d", &l, &r);
		if(l > r) std::swap(l, r);
		int tap, tbp; tap = 0;
		for(int j = l; j <= std::min(r, R(Pos[l])); j ++) {
			int tcp = Number_Counter(a[j], l, r);
			if(tap < tcp) tbp = a[j], tap = tcp;
			else if(tap == tcp && Re_a[a[j]] < Re_a[tbp]) tbp = a[j], tap = tcp;

		} if(Pos[l] != Pos[r])
			for(int j = r; j >= std::max(l, L(Pos[r])); j --) {
				int tcp = Number_Counter(a[j], l, r);
				if(tap < tcp) tbp = a[j], tap = tcp;
				else if(tap == tcp && Re_a[a[j]] < Re_a[tbp]) tbp = a[j], tap = tcp;
		
		} int tdp = F[Pos[l] + 1][Pos[r] - 1];
		int tcp = Number_Counter(tdp, l, r);
		if(tap < tcp) tbp = tdp, tap = tcp;
		else if(tap == tcp && Re_a[tdp] < Re_a[tbp]) tbp = tdp, tap = tcp;

		printf("%d\n", Re_a[tbp]);
	} return 0;

} int L(int p) {
	return std::max((p - 1) * Len + 1, 1);

} int R(int p) {
	return std::min(p * Len, n);

} void Init_F() {
	for(int i = 1; i <= Pos[n]; i ++) {
		int tap, tbp = 0; memset(r, 0, sizeof(r));
		for(int j = L(i); j <= n; j ++) {
			++r[a[j]];
			if(tbp < r[a[j]] || (tbp == r[a[j]] && Re_a[a[j]] < Re_a[tap])) tap = a[j], tbp = r[a[j]]; 
			F[i][Pos[j]] = tap;
		}
	}
} int Number_Counter(int Val, int l, int r) {
	int tap = std::lower_bound(Vc[Val].begin(), Vc[Val].end(), l) - Vc[Val].begin();
	int tbp = std::upper_bound(Vc[Val].begin(), Vc[Val].end(), r) - Vc[Val].begin();
	return tbp - tap;
}

后记

完结撒花!!!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!