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 來支援插入操作。
但是,當一個塊的大小過大時複雜度難以得到保證。
這時就需要重新分塊。
-
按時間重構。 每\(\sqrt n\)次操作過後暴力\(O(n)\)重構。
-
當某一塊超過設定的閥值時將其分裂。
查詢暴力跳塊統計排名即可。
-
#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;
}
後記
完結撒花!!!