[学习笔记]数列分块入门九题[LOJ6277-6285]
Thoughts
打完这九题,感觉脱了一层皮,各种或毒瘤或傻逼的错误,很难只交一次便通过。如果不看题解把这九题打完,不仅分块有所进步,调代码细节的能力也会提升。
我感觉到分块算法本身思维难度不大,主要是代码的细节问题。而要想用分块解决一个问题,最重要的是找到每个块到底存储什么,这些存储的值能否合并,怎样合并。
Solution
T1
- 大意
区间加法与单点查值
-
思路
- 分块入门第一题,是对分块算法的大致模式的练习。散块暴力,整块合并。
-
代码
#include <bits/stdc++.h>
const int N = 50000;
int n;
int opt;
int l;
int r;
int c;
int len;
int start;
int end;
int v[300];
int a[N + 30];
int p[N + 30];
inline void build()
{
len = sqrt(n);
for (int i = 1; i <= n; ++i)
p[i] = (i - 1) / len + 1;
}
inline void add(int L, int R, int s)
{
if (p[L] == p[R])
for (int i = L; i <= R; ++i)
a[i] += s;
else
{
for (int i = L; p[i] == p[L]; ++i)
a[i] += s;
for (int i = R; p[i] == p[R]; --i)
a[i] += s;
for (int i = p[L] + 1; i <= p[R] - 1; ++i)
v[i] += s;
}
}
int main()
{
std::cin >> n;
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
build();
for (int i = 1; i <= n; ++i)
{
std::cin >> opt>> l>> r>> c;
if (opt == 0)
add(l, r, c);
else
printf ("%d\n", v[p[r]] + a[r]);
}
return 0;
}
T2
-
大意
区间加法,询问区间小于某个值\(x\)的元素个数 -
思路
- 对于操作2,我们无法在原块上快速查询答案,因此我们要储存原块的有序版本,对于每个块内,都是有序的,但块与块之间没有关联。
- 保证块有序之后,我们依然对于散块进行暴力,整块则进行二分查找,每次操作2的复杂度就变为了\(O(\sqrt{n} \log n)\)
- 程序中的\(rebuild\)函数是在操作1暴力加散块之后,重构散块的有序版本
-
代码:
#include <vector>
#include <cmath>
#include <cstdio>
#include <algorithm>
const int N = 50030;
const int Block = 500;
int n, blo, opt, l, r, c, ans;
int a[N], p[N], v[Block];
std::vector <int> b[Block];
inline void rebuild(int num)
{
b[num].clear();
for (int i = blo * (num - 1) + 1; i <= std::min(blo * num, n); ++i)
b[num].push_back(a[i]);
std::sort(b[num].begin(), b[num].end());
}
int main()
{
scanf ("%d", &n);
blo = sqrt(n);
for (int i = 1; i <= n; ++i)
{
scanf ("%d", &a[i]);
p[i] = (i - 1) / blo + 1;
b[p[i]].push_back(a[i]);
}
for (int i = 1; i <= p[n]; ++i)
std::sort(b[i].begin(), b[i].end());
for (int t = 1; t <= n; ++t)
{
scanf ("%d%d%d%d", &opt, &l, &r, &c);
if (opt == 0)
{
for (int i = l; i <= std::min(blo * p[l], r); ++i)
a[i] += c;
rebuild(p[l]);
if (p[l] != p[r])
{hebing
for (int i = r; i > (p[r] - 1) * blo; --i)
a[i] += c;
rebuild(p[r]);
}
for (int i = p[l] + 1; i < p[r]; ++i)
v[i] += c;
}
else
{
ans = 0;
c *= c;
for (int i = l; i <= std::min(r, blo * p[l]); ++i)
ans += (a[i] + v[p[i]] < c);
for (int i = r; i > (p[r] - 1) * blo && p[l] != p[r]; --i)
ans += (a[i] + v[p[i]] < c);
for (int i = p[l] + 1; i < p[r]; ++i)
ans += std::lower_bound(b[i].begin(), b[i].end(), c - v[i]) - b[i].begin();
printf ("%d\n", ans);
}
}
return 0;
}
T3
-
大意
区间加法,询问区间内比某个值\(x\)小的最大元素 -
思路
- 与第2题相似,只需把询问小于\(x\)元素个数改为询问\(x\)前驱
-
代码
#include <vector>
#include <cmath>
#include <cstdio>
#include <algorithm>
const int N =100030;
const int Block = 500;
int n, blo, opt, l, r, c, x, ans;
int a[N], p[N], v[Block];
std::vector <int> b[Block];
inline void rebuild(int num)
{
b[num].clear();
for (int i = blo * (num - 1) + 1; i <= std::min(blo * num, n); ++i)
b[num].push_back(a[i]);
std::sort(b[num].begin(), b[num].end());
}
int main()
{
scanf ("%d", &n);
blo = sqrt(n);
for (int i = 1; i <= n; ++i)
{
scanf ("%d", &a[i]);
p[i] = (i - 1) / blo + 1;
b[p[i]].push_back(a[i]);
}
for (int i = 1; i <= p[n]; ++i)
std::sort(b[i].begin(), b[i].end());
for (int t = 1; t <= n; ++t)
{
scanf ("%d%d%d%d", &opt, &l, &r, &c);
if (opt == 0)
{
for (int i = l; i <= std::min(blo * p[l], r); ++i)
a[i] += c;
rebuild(p[l]);
if (p[l] != p[r])
{
for (int i = r; i > (p[r] - 1) * blo; --i)
a[i] += c;
rebuild(p[r]);
}
for (int i = p[l] + 1; i < p[r]; ++i)
v[i] += c;
}
else
{
ans = -1;
for (int i = l; i <= std::min(r, blo * p[l]); ++i)
if (a[i] + v[p[i]] < c)
ans = std::max(a[i] + v[p[i]], ans);
for (int i = r; i > (p[r] - 1) * blo && p[l] != p[r]; --i)
if (a[i] + v[p[i]] < c)
ans = std::max(a[i] + v[p[i]], ans);
for (int i = p[l] + 1; i < p[r]; ++i)
if (c - v[i] > b[i][0])
{
x = std::lower_bound(b[i].begin(), b[i].end(), c - v[i]) - b[i].begin();
ans = std::max(b[i][x - 1] + v[i], ans);
}
printf ("%d\n", ans);
}
}
return 0;
}
T4
-
大意
区间加法与区间求和 -
思路
- 储存每个块内的元素和,散块暴力,整块合并
-
代码
#include <vector>
#include <cmath>
#include <cstdio>
#include <algorithm>
const int N =100030;
const int Block = 500;
typedef long long LL;
int n, blo, opt, l, r, x;
int p[N];
LL ans, c;
LL a[N], v[Block], b[Block];
int main()
{
scanf ("%d", &n);
blo = sqrt(n);
for (int i = 1; i <= n; ++i)
{
scanf ("%lld", &a[i]);
p[i] = (i - 1) / blo + 1;
b[p[i]] += a[i];
}
for (int t = 1; t <= n; ++t)
{
scanf ("%d%d%d%lld", &opt, &l, &r, &c);
if (opt == 0)
{
for (int i = l; i <= std::min(blo * p[l], r); ++i)
{
a[i] += c;
b[p[i]] += c;
}
for (int i = r; i > (p[r] - 1) * blo && p[l] != p[r]; --i)
{
a[i] += c;
b[p[i]] += c;
}
for (int i = p[l] + 1; i < p[r]; ++i)
v[i] += c;
}
else
{
ans = 0;
for (int i = l; i <= std::min(r, blo * p[l]); ++i)
ans = (ans + a[i] + v[p[i]]) % (c + 1);
for (int i = r; i > (p[r] - 1) * blo && p[l] != p[r]; --i)
ans = (ans + a[i] + v[p[i]]) % (c + 1);
for (int i = p[l] + 1; i < p[r]; ++i)
ans = (ans + b[i] + v[i] * blo) % (c + 1);
printf ("%lld\n", ans);
}
}
return 0;
}
T5
-
大意
区间开方,区间求和 -
思路
- 每个数开方,开到几次就变成1了,显然可以暴力。
- 只需记录下每个块内是否都开完了(即是否全变1了),开完了的话就跳过,如果没有就暴力开。
-
代码
#include <vector>
#include <cmath>
#include <cstdio>
#include <algorithm>
const int N =100030;
const int Block = 500;
typedef long long LL;
int n, blo, opt, l, r, c, x;
int p[N], a[N], cnt[Block];
LL ans;
int main()
{
scanf ("%d", &n);
blo = sqrt(n);
for (int i = 1; i <= n; ++i)
{
scanf ("%d", &a[i]);
p[i] = (i - 1) / blo + 1;
cnt[p[i]] += (a[i] == 1);
}
for (int t = 1; t <= n; ++t)
{
scanf ("%d%d%d%d", &opt, &l, &r, &c);
if (opt == 0)
{
for (int i =hebing l; i <= std::min(blo * p[l], r); ++i)
{
if (a[i] > 1)
{
a[i] = sqrt(a[i]);
cnt[p[i]] += (a[i] == 1);
}
}
for (int i = r; i > (p[r] - 1) * blo && p[l] != p[r]; --i)
{
if (a[i] > 1)
{
a[i] = sqrt(a[i]);
cnt[p[i]] += (a[i] == 1);
}
}
for (int i = p[l] + 1; i < p[r]; ++i)
for (int j = (i - 1) * blo + 1; j <= std::min(n, i * blo) && cnt[i] != blo; ++j)
if (a[j] > 1)
{
a[j] = sqrt(a[j]);
cnt[p[j]] += (a[j] == 1);
}
}
else
{
ans = 0;
for (int i = l; i <= std::min(r, blo * p[l]); ++i)
ans += a[i];
for (int i = r; i > (p[r] - 1) * blo && p[l] != p[r]; --i)
ans += a[i];
for (int i = p[l] + 1; i < p[r]; ++i)
if (cnt[i] == blo)
ans += blo;
else
for (int j = (i - 1) * blo + 1; j <= std::min(n, i * blo); ++j)
ans += a[j];
printf ("%lld\n", ans);
}
}
return 0;
}
T6
-
大意
单点插入,单点查询 -
思路
- 本题每个块用一个\(vector\),要插入某元素,则在其所在块用\(insert\)函数插入
- 要想找到其所在块,只需从第一个块开始遍历,每次给插入位置减去当前块的大小即可
- 值得一提的是,这样做无法保证块的大小都相同,如果本题在某一个块中插入的元素个数过多,比如说我每次都插到同一个块里去,那个块大小就变得很大,这样就会超时。解决办法则是进行定期重构,当插入了\(\sqrt{n}\)元素时,就对所有块进行重构,调整每个块的大小
不过这个题只开一个块,直接用vector进行insert也能过。。。。 -
代码
这题我压行了,,,,,,,
#include <bits/stdc++.h>
const int N =100030;
int n, blo, opt, l, r, c, x, m, num, sum, top, a[N], st[N * 2];
std::vector < int > b[N];
inline void rebuild() {
sum = top = 0;
for (int i = 1; i <= m; ++i) {
for (std::vector < int >::iterator it = b[i].begin(); it != b[i].end(); ++it) st[++top] = *it;
b[i].clear();
}int block2 = sqrt(top);
for (int i = 1; i <= top; ++i) b[(i - 1) / block2 + 1].push_back(st[i]);
m = (top - 1) / block2 + 1;
}int main() {
scanf ("%d", &n);
blo = sqrt(n);
for (int i = 1; i <= n; ++i){
scanf ("%d", &a[i]);
b[(i - 1) / blo + 1].push_back(a[i]);
}m = (n - 1) / blo + 1;
for (int t = 1; t <= n; ++t) {
scanf ("%d%d%d%d", &opt, &l, &r, &c);
if (opt == 0){
num = 1;
while (l > b[num].size()) l -= b[num++].size();
b[num].insert(b[num].begin() + l - 1, r);
if (++sum == blo) rebuild();
}else {
x = 1;
while (r > b[x].size()) r -= b[x++].size();
printf ("%d\n", b[x][r - 1]);
}
}return 0;
}
T7
-
大意
区间乘法,区间加法,单点询问
-
思路
- 两个标记,一个加法,一个乘法,先乘后加,在进行暴力散块时,要把散块所在的整个块的标记下传后再进行暴力乘法加法
-
代码
这题我又压了,压行真的还挺爽的
#include <bits/stdc++.h>
const int N = 1000000, Blo = 5000, MOD = 10007;
int n, l, r, c, opt, blo, p[N], fir[Blo], end[Blo];
long long add[Blo], mul[Blo], a[N];
int main() {
scanf ("%d", &n); blo = sqrt(n);
for (int i = 1; i <= n; ++i) {
scanf ("%lld", &a[i]);
p[i] = (i - 1) / blo + 1;
if (fir[p[i]] == 0) fir[p[i]] = i;
end[p[i]] = i; mul[p[i]] = 1;
} for (int i = 1; i <= n; ++i) {
scanf ("%d%d%d%d", &opt, &l, &r, &c);
if (opt <= 1) {
for (int i = fir[p[l]]; i <= end[p[l]]; ++i) {
if (i < l || i > std::min(r,end[p[l]])) a[i] = (a[i] * mul[p[i]] + add[p[i]]) % MOD;
else if (opt == 0) a[i] = (a[i] * mul[p[i]] + add[p[i]] + c) % MOD;
else a[i] = (a[i] * mul[p[i]] * c + add[p[i]] * c) % MOD;
if (i == end[p[l]]) {mul[p[i]] = 1; add[p[i]] = 0;}
}for (int i = end[p[r]]; i >= fir[p[r]] && p[l] != p[r]; --i) {
if (i > r) a[i] = (a[i] * mul[p[i]] + add[p[i]]) % MOD;
else if (opt == 0) a[i] = (a[i] * mul[p[i]] + add[p[i]] + c) % MOD;
else a[i] = (a[i] * mul[p[i]] * c + add[p[i]] * c) % MOD;
if (i == fir[p[r]]) {mul[p[i]] = 1; add[p[i]] = 0;}
}for (int i = p[l] + 1; i < p[r]; ++i)
if (opt == 0) add[i] = (add[i] + c) % MOD;
else {mul[i] = (mul[i] * c) % MOD; add[i] = (add[i] * c) % MOD; }
}else printf ("%lld\n", (a[r] * mul[p[r]] + add[p[r]]) % MOD);
}return 0;
}
T8
-
大意
区间询问等于一个数\(c\)的元素个数,再把这个区间内所有元素改为\(c\)
-
思路
- 记录下这个区间是否的元素是否全是同一个数,是则赋值为该值,不是则赋值为INF
-
代码
#include <cstdio>
#include <algorithm>
#include <cmath>
typedef long long LL;
const int N = 1e5 + 30, Blo = 5000;
const LL INF = 1e12 + 30;
int n, l, r, c, blo, ans;
int p[N];
LL a[N], now[Blo];
int main()
{
scanf ("%d", &n);
blo = sqrt(n);
for (int i = 1; i <= n; ++i)
{
scanf ("%lld", &a[i]);
p[i] = (i - 1) / blo + 1;
now[p[i]] = INF;
}
for (int t = 1; t <= n; ++t)
{
ans = 0;
scanf ("%d%d%d", &l, &r, &c);
for (int i = (p[l] - 1) * blo + 1; i <= std::min(n, p[l] * blo); ++i)
{
if ((i < l || i > r) && now[p[i]] != INF)
a[i] = now[p[i]];
else if (i >= l && i <= r)
{
ans += ((a[i] == c && now[p[i]] == INF) || now[p[i]] == c);
a[i] = c;
}
}
for (int i = (p[r] - 1) * blo + 1; i <= std::min(n, p[r] * blo) && p[l] != p[r]; ++i)
{
if (i > r && now[p[i]] != INF)
a[i] = now[p[i]];
else if (i <= r && i >= l)
{
ans += ((a[i] == c && now[p[i]] == INF) || now[p[i]] == c);
a[i] = c;
}
}
now[p[l]] = now[p[r]] = INF;
for (int i = p[l] + 1; i < p[r]; ++i)
{
if (now[i] == c) ans += blo;
else if (now[i] == INF)
{
for (int j = (i - 1) * blo + 1; j <= i * blo; ++j)
ans += (a[j] == c);
}
now[i] = c;
}
printf ("%d\n", ans);
}
return 0;
}
T9
-
大意
区间最小众数
-
思路
-
考虑区间最小众数,答案肯定为区间内整块部分的唯一一个最小众数或者是散块里所有出现过的数中的一个
-
为什么呢,理性证明请百度陈立杰的区间最小众数解题报告。这里我们感性理解下,对于整块部分的那个众数,不用想,它肯定是有可能成为答案的,这个时候任何一个整块部分里的数都无法替代它,替代它的唯一可能就是再增加自己的出现次数,这种情况是只有它在散块出现里才有可能的。
-
-
代码
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <map>
#include <cstring>
#include <vector>
const int N = 100030;
const int BLOCK = 2030;
bool vis[N];
int n, blo, id, mx, f, l, r, x;
int a[N], Ans[N], m[BLOCK][BLOCK], p[N], num[N];
std::map <int, int > mp;
std::vector < int > v[N];
int get(int left, int right, int now)
{
return std::upper_bound(v[now].begin(), v[now].end(), right) - std::lower_bound(v[now].begin(), v[now].end(), left);
}
int main()
{
scanf ("%d", &n);
blo = 80;
for (int i = 1; i <= n; ++i)
{
scanf ("%d", &a[i]);
if (mp[a[i]] == 0)
{
mp[a[i]] = ++id;
Ans[id] = a[i];
}
p[i] = (i - 1) / blo + 1;
a[i] = mp[a[i]];
v[a[i]].push_back(i);
}
for (int i = 1; i <= p[n]; ++i)
{
std::memset(num, 0 ,sizeof(num));
mx = f = 0;
for (int j = (i - 1) * blo + 1; j <= n; ++j)
{
num[a[j]]++;
if (num[a[j]] > f || (num[a[j]] == f && Ans[a[j]] < Ans[mx]))
{
mx = a[j];Markdown Theme Kit
}
for (int t = 1; t <= n; ++t)
{
scanf ("%d%d", &l, &r);
mx = get(l, r, m[p[l] + 1][p[r] - 1]);
f = m[p[l] + 1][p[r] - 1];
std::memset(vis, 0, sizeof(vis));
vis[f] = 1;
for (int i = l; i <= std::min(r, p[l] * blo); ++i)
{
if (vis[a[i]] == 0)
{
vis[a[i]] = 1;
x = get(l, r, a[i]);
if (mx < x || mx == x && Ans[a[i]] < Ans[f])
{
f = a[i];
mx = x;
}
}
}
for (int i = (p[r] - 1) * blo + 1; i <= r && p[l] != p[r]; ++i)
{
if (vis[a[i]] == 0)
{
vis[a[i]] = 1;
x = get(l, r, a[i]);
if (mx < x || mx == x && Ans[a[i]] < Ans[f])
{
f = a[i];
mx = x;
}
}
}
printf ("%d\n", Ans[f]);
}
return 0;
}
Ending
码字不易,欢迎点赞和评论qvq