1. 程式人生 > 實用技巧 >LOJ數列分塊1-9全家桶

LOJ數列分塊1-9全家桶

背景

終於全艹過去了……累死我了……

首先感謝 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[j].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

    做過洛谷上的線段樹3後對這道題當然也不怕。

    每一個塊維護兩個標記(加法與乘法):\(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;
}

後記

完結撒花!!!