大佬总结
数列分块入门 1
#include<bits/stdc++.h>
#define L(i) (bl[i] - 1) * blo + 1
#define R(i) bl[i] * blo
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 5e4 + 10;
int a[MAXN], bl[MAXN], tag[MAXN];
int blo, n, m;void add(int l, int r, int k)
{if(bl[l] == bl[r]){_for(i, l, r) a[i] += k;return;}_for(i, l, R(l)) a[i] += k;_for(i, L(r), r) a[i] += k;_for(i, bl[l] + 1, bl[r] - 1) tag[i] += k;
}int main()
{scanf("%d", &n); blo = sqrt(n);_for(i, 1, n){scanf("%d", &a[i]);bl[i] = (i - 1) / blo + 1;}while(n--){int op, l, r, c; scanf("%d%d%d%d", &op, &l, &r, &c);if(op == 0) add(l, r, c);else printf("%d\n", a[r] + tag[bl[r]]);}return 0;
}
数列分块入门 2
可以对每一个块进行排序,用vector存储每一个块的信息
可以发现lowerbond的值恰好就是小于x的个数
#include<bits/stdc++.h>
#define L(i) (i - 1) * blo + 1
#define R(i) min(i * blo, n)
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 5e4 + 10;
const int MAXM = 250;
int a[MAXN],bl[MAXN], tag[MAXN];
int blo, n, m;
vector<int> ve[MAXM];inline void s(int k)
{ ve[k].clear();_for(i, L(k), R(k)) ve[k].push_back(a[i]);sort(ve[k].begin(), ve[k].end());
}inline void add(int l, int r, int k)
{if(bl[l] == bl[r]){_for(i, l, r) a[i] += k; s(bl[l]);return;}_for(i, l, R(bl[l])) a[i] += k; s(bl[l]);_for(i, L(bl[r]), r) a[i] += k; s(bl[r]);_for(i, bl[l] + 1, bl[r] - 1) tag[i] += k;
}inline int query(int l, int r, int k)
{int res = 0;if(bl[l] == bl[r]){_for(i, l, r) if(a[i] + tag[bl[l]] < k)res++;return res;}_for(i, l, R(bl[l])) if(a[i] + tag[bl[l]] < k) res++;_for(i, L(bl[r]), r) if(a[i] + tag[bl[r]] < k) res++;_for(i, bl[l] + 1, bl[r] - 1)res += lower_bound(ve[i].begin(), ve[i].end(), k - tag[i]) - ve[i].begin();return res;
}void init()
{blo = sqrt(n);for(int i = 1; i <= n; i++) bl[i] = (i - 1) / blo + 1;_for(i, 1, bl[n]) s(i);
}int main()
{scanf("%d", &n);_for(i, 1, n) scanf("%d", &a[i]);init();REP(i, 0, n){int op, l, r, c; scanf("%d%d%d%d", &op, &l, &r, &c);if(op == 0) add(l, r, c);else printf("%d\n", query(l, r, c * c));}return 0;
}
数列分块入门 3
用set简直太方便!!比vector方便多了!!set可以自动排序,增加删除都很方便。
#include<bits/stdc++.h>
#define L(i) (i - 1) * blo + 1
#define R(i) min(i * blo, n)
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 1e5 + 10;
const int MAXM = 350;
int a[MAXN], bl[MAXN], tag[MAXM];
int blo, n, m;
set<int> s[MAXM];inline void motify(int x, int q) //第x个元素加上q
{s[bl[x]].erase(a[x]);a[x] += q;s[bl[x]].insert(a[x]);
}inline void add(int l, int r, int k)
{if(bl[l] == bl[r]){_for(i, l, r) motify(i, k);return;}_for(i, l, R(bl[l])) motify(i, k);_for(i, L(bl[r]), r) motify(i, k);_for(i, bl[l] + 1, bl[r] - 1) tag[i] += k;
}inline void update(int now, int& res, int k)
{if(now < k && now > res) res = now;
}inline int query(int l, int r, int k)
{int res = -1;if(bl[l] == bl[r]){_for(i, l, r) update(a[i] + tag[bl[l]], res, k);return res;}_for(i, l, R(bl[l])) update(a[i] + tag[bl[l]], res, k);_for(i, L(bl[r]), r) update(a[i] + tag[bl[r]], res, k);_for(i, bl[l] + 1, bl[r] - 1){set<int>::iterator it = s[i].lower_bound(k - tag[i]);if(it == s[i].begin()) continue; it--;update(*it + tag[i], res, k);}return res;
}void init()
{blo = sqrt(n);for(int i = 1; i <= n; i++) {bl[i] = (i - 1) / blo + 1;s[bl[i]].insert(a[i]);}
}int main()
{scanf("%d", &n);_for(i, 1, n) scanf("%d", &a[i]);init();REP(i, 0, n){int op, l, r, c; scanf("%d%d%d%d", &op, &l, &r, &c);if(op == 0) add(l, r, c);else printf("%d\n", query(l, r, c));}return 0;
}
数列分块入门 4
多开一个sum数组。涉及到区间修改开tag数组,涉及到区间求和用sum数组
记得开long long
#include<bits/stdc++.h>
#define L(i) (i - 1) * blo + 1
#define R(i) min(i * blo, n)
#define cal(a, b) a = (a + b) % MOD
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int MAXN = 5e4 + 10;
const int MAXM = 250;
ll a[MAXN], sum[MAXM], tag[MAXN];
int bl[MAXN], blo, n, m;inline void add(int l, int r, int k)
{if(bl[l] == bl[r]){_for(i, l, r) a[i] += k, sum[bl[l]] += k; return;}_for(i, l, R(bl[l])) a[i] += k, sum[bl[l]] += k; _for(i, L(bl[r]), r) a[i] += k, sum[bl[r]] += k; _for(i, bl[l] + 1, bl[r] - 1) tag[i] += k;
}inline ll query(int l, int r, int MOD)
{ll res = 0;if(bl[l] == bl[r]){_for(i, l, r) cal(res, a[i] + tag[bl[l]]);return res;}_for(i, l, R(bl[l])) cal(res, a[i] + tag[bl[l]]);_for(i, L(bl[r]), r) cal(res, a[i] + tag[bl[r]]);_for(i, bl[l] + 1, bl[r] - 1) cal(res, sum[i] + blo * tag[i]);return res;
}void init()
{blo = sqrt(n);for(int i = 1; i <= n; i++) {bl[i] = (i - 1) / blo + 1;sum[bl[i]] += a[i];}
}int main()
{scanf("%d", &n);_for(i, 1, n) scanf("%d", &a[i]);init();REP(i, 0, n){int op, l, r, c; scanf("%d%d%d%d", &op, &l, &r, &c);if(op == 0) add(l, r, c);else printf("%lld\n", query(l, r, c + 1));}return 0;
}
数列分块入门5
正好前几天做过一模一样的题,是用线段树写的。核心就是很多数开方几次就变成1了,分块的时候可以看如果sum[i]==blo的话说明全部是1,就直接跳过就好了。其实应该有0的情况,所以应该写sum[i] > blo才执行,但是不知道为什么我这么写以后拿了80分。
#include<bits/stdc++.h>
#define L(i) (i - 1) * blo + 1
#define R(i) min(i * blo, n)
#define cal(a, b) a = (a + b) % MOD
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int MAXN = 5e4 + 10;
const int MAXM = 250;
ll a[MAXN], sum[MAXM];
int bl[MAXN], blo, n, m;inline void updata(int i)
{sum[bl[i]] -= a[i] - sqrt(a[i]);a[i] = sqrt(a[i]);
}inline void change(int l, int r)
{if(bl[l] == bl[r]){if(sum[bl[l]] == blo) return;_for(i, l, r) updata(i);return;}if(sum[bl[l]] != blo)_for(i, l, R(bl[l])) updata(i);if(sum[bl[r]] != blo)_for(i, L(bl[r]), r) updata(i);_for(i, bl[l] + 1, bl[r] - 1){if(sum[i] == blo) continue;_for(j, L(i), R(i))updata(j);}
}inline ll query(int l, int r)
{ll res = 0;if(bl[l] == bl[r]){_for(i, l, r) res += a[i];return res;}_for(i, l, R(bl[l])) res += a[i];_for(i, L(bl[r]), r) res += a[i];_for(i, bl[l] + 1, bl[r] - 1) res += sum[i];return res;
}void init()
{blo = sqrt(n);for(int i = 1; i <= n; i++) {bl[i] = (i - 1) / blo + 1;sum[bl[i]] += a[i];}
}int main()
{scanf("%d", &n);_for(i, 1, n) scanf("%d", &a[i]);init();REP(i, 0, n){int op, l, r, c; scanf("%d%d%d%d", &op, &l, &r, &c);if(op == 0) change(l, r);else printf("%lld\n", query(l, r));}return 0;
}
数列分块入门6
不得不说,这道题真的
非常暴力!!!!
这样也可以过?????
看来我低估了数据强度()
因为vector支持在中间插入,所以用vector来存每一块的内容
然后每加入一个数就找到时第几块第几个位置(注意这里vector下标从0开始,我因此WA一次)
然后加入就好了。
这里有个技巧是重构
就是当一个块很多的时候,复杂度会很大,这个时候可以重构,O(n)
复杂度分析:构建分块O(n),每次查询根号n,每次加入根号n
所以是n的1.5次方
复杂度貌似没有问题……我想到了这个方法但是感觉太暴力就没敢写
分块是一个优秀的暴力……
反思
(1)没有算复杂度的习惯,导致想到正解却不敢写
(2)随机数据代表不会有一些专门卡你的数据,数据比较弱
(3)vector加入操作
(4)分块的重构技巧
(5)写完后静态查错。
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 2e5 + 10;
const int MAXM = 500;
vector<int> ve[MAXM];
int tmp[MAXN], num, n, blo, m;void read(int& x)
{int f = 1; x = 0; char ch = getchar();while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }x *= f;
}void rebulid()
{num = 0;_for(i, 1, m){REP(j, 0, ve[i].size())tmp[++num] = ve[i][j]; ve[i].clear();}blo = sqrt(num);_for(i, 1, num){int t = (i - 1) / blo + 1;ve[t].push_back(tmp[i]);}m = (num - 1) / blo + 1;
}void add(int l, int c)
{int x = 1;while(l > ve[x].size()) //这个定位的方法很棒 l -= ve[x++].size();ve[x].insert(ve[x].begin() + l - 1, c);if(ve[x].size() > blo * 10) rebulid();
}int query(int l)
{int x = 1;while(l > ve[x].size()) l -= ve[x++].size();return ve[x][l-1];
}int main()
{read(n);blo = sqrt(n);_for(i, 1, n){int x; read(x);int t = (i - 1) / blo + 1;ve[t].push_back(x);}m = (n - 1) / blo + 1;_for(i, 1, n){int op, l, r, c;read(op); read(l); read(r); read(c); if(op == 0) add(l, r);else printf("%d\n", query(r));}return 0;
}
数列分块入门 7
这道题之前做过,还想了好久……方法有些问题……
先乘后加,如果乘的话要把加法标记一起乘了。
然后如果暴力的话,那么就要把标记的值传下来然后再做,然后标记清零。
#include<bits/stdc++.h>
#define L(i) (i - 1) * blo + 1
#define R(i) min(n, i * blo)
#define a(x, y) x = (x + y) % MOD
#define m(x, y) x = (x * y) % MOD
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 1e5 + 10;
const int MAXM = 500;
const int MOD = 10007;
int bl[MAXN], tag[MAXM], ftag[MAXM], a[MAXN];
int blo, n;void read(int& x)
{int f = 1; x = 0; char ch = getchar();while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }x *= f;
}inline int query(int r)
{return (a[r] * ftag[bl[r]] % MOD + tag[bl[r]]) % MOD;
}void reset(int x)
{_for(i, L(x), R(x))a[i] = query(i);tag[x] = 0; ftag[x] = 1;
}void add(int l, int r, int c)
{if(bl[l] == bl[r]){reset(bl[l]);_for(i, l, r) a(a[i], c);return;}reset(bl[l]); _for(i, l, R(bl[l])) a(a[i], c);reset(bl[r]); _for(i, L(bl[r]), r) a(a[i], c);_for(i, bl[l] + 1, bl[r] - 1) a(tag[i], c);
}void mul(int l, int r, int c)
{if(bl[l] == bl[r]){reset(bl[l]);_for(i, l, r) m(a[i], c);return;}reset(bl[l]); _for(i, l, R(bl[l])) m(a[i], c);reset(bl[r]); _for(i, L(bl[r]), r) m(a[i], c);_for(i, bl[l] + 1, bl[r] - 1) m(tag[i], c), m(ftag[i], c);
}int main()
{read(n);blo = sqrt(n);_for(i, 1, n) {read(a[i]);bl[i] = (i - 1) / blo + 1;}_for(i, 1, bl[n]) ftag[i] = 1;_for(i, 1, n){int op, l, r, c;read(op); read(l); read(r); read(c); if(op == 0) add(l, r, c);else if(op == 1) mul(l, r, c);else printf("%d\n", query(r));}return 0;
}
数列分块入门8
足足写了两个半小时。
开始看到统计个数,想到二分,用lower_bound和upper_bound的差值。
所以用,multiset,也就是可以加入重复元素的set
然后就一直调,一直WA,各种各样的细节错误
后来发现懒标记改掉的时候我没有改变multiset里面的值
然后发现这样还不如直接用数组。
非常非常暴力的求值
加速就加在懒标记,统计的时候便利块的时候可以O(1)过去
真的好暴力,我想复杂了。
暴力出奇迹!!
#include<bits/stdc++.h>
#define L(i) ((i - 1) * blo + 1)
#define R(i) min(n, i * blo)
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 1e5 + 10;
int bl[MAXN], a[MAXN], tag[MAXN];
int blo, n;void read(int& x)
{int f = 1; x = 0; char ch = getchar();while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }x *= f;
}void reset(int x, int c)
{_for(i, L(x), R(x))a[i] = c;tag[x] = -1;
}void deal(int i, int c, int& res)
{if(tag[bl[i]] != -1) reset(bl[i], tag[bl[i]]);if(a[i] == c) res++;a[i] = c;
}int query(int l, int r, int c)
{int res = 0;if(bl[l] == bl[r]){_for(i, l, r) deal(i, c, res);return res;}_for(i, l, R(bl[l])) deal(i, c, res);_for(i, L(bl[r]), r) deal(i, c, res);_for(i, bl[l] + 1, bl[r] - 1){if(tag[i] != -1) res += (tag[i] == c) ? blo : 0;else _for(j, L(i), R(i)) deal(j, c, res); tag[i] = c;}return res;
}int main()
{memset(tag, -1, sizeof(tag)); read(n); blo = sqrt(n);_for(i, 1, n) {read(a[i]);bl[i] = (i - 1) / blo + 1;}_for(k, 1, n){int l, r, c;read(l); read(r); read(c); printf("%d\n", query(l, r, c));}return 0;
}
数列分块入门9(区间众数)
这道题真的秀!!
思考分块算法的时候分两个部分,一个是块内,一个是多余部分
对于这道题,设块大小为S
(1)块内可以暴力预处理出来,N * N / S
(2)多余部分可以用vector+二分的骚操作弄。
对于每个数值建一个vector,插入位置。
然后查询的时候用当前左端点和右端点二分的差值就是个数。
很秀!
复杂度NlogNS
所以总的复杂度是 O(N * N / S + NlogNS)
当N * N / S = NlogNS时最小
得S = 根号下N /logN
注意这里如果用根号N的话会TLE。
最后注意要离散化一下。
#include<bits/stdc++.h>
#define L(i) ((i - 1) * blo + 1)
#define R(i) min(n, i * blo)
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;const int MAXN = 1e5 + 10;
const int MAXM = 2e3 + 10;int bl[MAXN], a[MAXN], b[MAXN];
int pos[MAXN], f[MAXM][MAXM];
int p[MAXN], buck[MAXN], blo, n;vector<int> ve[MAXN];void read(int& x)
{int f = 1; x = 0; char ch = getchar();while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }x *= f;
}void updata(int times, int& cnt, int& ans, int now)
{if(times > cnt) cnt = times, ans = now;else if(times == cnt && now < ans) ans = now;
}int time(int x, int l, int r)
{int a = lower_bound(ve[x].begin(), ve[x].end(), l) - ve[x].begin();int b = upper_bound(ve[x].begin(), ve[x].end(), r) - ve[x].begin();return b - a;
}int query(int l, int r)
{if(bl[l] == bl[r]){int ans = 0, cnt = 0;_for(i, l, r)updata(time(a[i], l, r), cnt, ans, a[i]);return ans;}int ans = f[bl[l] + 1][bl[r] - 1]; int cnt = time(ans, l, r); _for(i, l, R(bl[l])) updata(time(a[i], l, r), cnt, ans, a[i]);_for(i, L(bl[r]), r) updata(time(a[i], l, r), cnt, ans, a[i]);return ans;
}void Read()
{read(n); blo = sqrt(n / log2(n));_for(i, 1, n) {read(a[i]);bl[i] = (i - 1) / blo + 1;b[i] = a[i];}
}void Disperse()
{sort(b + 1, b + n + 1);int m = unique(b + 1, b + n + 1) - b - 1; _for(i, 1, n){int x = lower_bound(b + 1, b + m + 1, a[i]) - b;p[x] = a[i];a[i] = x;}
}void deal(int x)
{int ans = 0, cnt = 0;memset(buck, 0, sizeof(buck));_for(i, L(x), n){buck[a[i]]++;updata(buck[a[i]], cnt, ans, a[i]);f[x][bl[i]] = ans; }
}void work()
{_for(i, 1, bl[n]) deal(i);_for(i, 1, n) ve[a[i]].push_back(i);_for(q, 1, n){int l, r;read(l); read(r);printf("%d\n", p[query(l, r)]);}
}int main()
{Read();Disperse();work();return 0;
}
bzoj 2038 (莫队算法)
这就是分块的另一种应用了,对询问进行分块,也就是莫队算法。
这个算法的精髓在于可以利用之前求出的结果离线查询,知道[L,R]
的每次可以答案O(1)的时间对端点拓展一个单位
要注意左端点右端点拓展的方式不同。
然后注意一些细节
(1)n和m的意义要非常清楚,不要搞混
(2) 注意有些地方会爆int, 尤其乘法的时候。
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int MAXN = 5e4 + 10;
int color[MAXN], bl[MAXN], sum[MAXN];
int blo, n, m;
ll ans;
struct node
{int l, r, id;ll a, b;bool operator < (const node& rhs) const{if(bl[l] != bl[rhs.l]) return bl[l] < bl[rhs.l];return r < rhs.r;}
}a[MAXN];void read(int& x)
{int f = 1; x = 0; char ch = getchar();while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }while(isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); }x *= f;
}bool cmp(node a, node b) { return a.id < b.id; }ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); }inline void motify(int x, int t)
{ans += (sum[color[x]] << 1) * t + 1;sum[color[x]] += t;
}int main()
{read(n); read(m); blo = sqrt(n);_for(i, 1, n) {read(color[i]);bl[i] = (i - 1) / blo + 1;}_for(i, 1, m){a[i].id = i;read(a[i].l); read(a[i].r);}sort(a + 1, a + m + 1);int last_l = 1, last_r = 0;_for(i, 1, m){a[i].b = (ll)(a[i].r - a[i].l) * (a[i].r - a[i].l + 1);while(last_r < a[i].r) motify(++last_r, 1);while(last_r > a[i].r) motify(last_r--, -1);while(last_l < a[i].l) motify(last_l++, -1);while(last_l > a[i].l) motify(--last_l, 1);a[i].a = ans - (a[i].r - a[i].l + 1);}sort(a + 1, a + m + 1, cmp);_for(i, 1, m){ll t = gcd(a[i].a, a[i].b);printf("%lld/%lld\n", a[i].a / t, a[i].b / t);}return 0;
}