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