背景
终于全艹过去了……累死我了……
首先感谢 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[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;
}
后记
完结撒花!!!
来源:oschina
链接:https://my.oschina.net/u/4316091/blog/4525316