分块思想基础莫队
分块
将数组分成sqrt(n)块,每次进行区间操作或者查询的时候,对于完整的块可以通过预处理的信息o1得到,
不完整的块直接暴力跑,所以最坏复杂度是sqrt(n)。
分块模板
const int N = 100010, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
void init(int n) {
block = sqrt(n + 0.5);
for (int i = 1; i <= block; i++) {
st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
ed[i] = n / block * i;
}
ed[block] = n;
for (int i = 1; i <= block; i++) {
for (int j = st[i]; j <= ed[i]; j++) {
bel[j] = i;//编号
//sum[i] += a[j];
}
sz[i] = ed[i] - st[i] + 1; //大小
}
}
查询和修改操作
int query(int l, int r) {
int ans = 0;
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) {
ans += a[i] + tag[bel[i]];
}
return ans;
} else {
for (int i = l; i <= ed[bel[l]]; i++) {
ans += a[i] + tag[bel[i]];
}
for (int i = st[bel[r]]; i <= r; i++) {
ans += a[i] + tag[bel[i]];
}
int L = bel[l] + 1, R = bel[r] - 1;
for (int i = L; i <= R; i++) {
ans += sum[i] + sz[i] * tag[i];
}
return ans;
}
}
void modify(int l, int r, int x) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) {//在一个快
a[i] += x;
sum[bel[i]] += x;
}
} else {
for (int i = l; i <= ed[bel[l]]; i++) { //左边
a[i] += x;
sum[bel[i]] += x;
}
for (int i = st[bel[r]]; i <= r; i++) { //右边
a[i] += x;
sum[bel[i]] += x;
}
int L = bel[l] + 1, R = bel[r] - 1;
for (int i = L; i <= R; i++) {
// sum[i] += sz[i] * x;
tag[i] += x;
}
}
}
P3372 【模板】线段树 1
直接套用模板即可
const int N = 100010, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
void init(int n) {
block = sqrt(n + 0.5);
for (int i = 1; i <= block; i++) {
st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
ed[i] = n / block * i;
}
ed[block] = n;
for (int i = 1; i <= block; i++) {
for (int j = st[i]; j <= ed[i]; j++) {
bel[j] = i;//编号
sum[i] += a[j];
}
sz[i] = ed[i] - st[i] + 1; //大小
}
}
int query(int l, int r) {
int ans = 0;
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) {
ans += a[i] + tag[bel[i]];
}
return ans;
} else {
for (int i = l; i <= ed[bel[l]]; i++) {
ans += a[i] + tag[bel[i]];
}
for (int i = st[bel[r]]; i <= r; i++) {
ans += a[i] + tag[bel[i]];
}
int L = bel[l] + 1, R = bel[r] - 1;
for (int i = L; i <= R; i++) {
ans += sum[i] + sz[i] * tag[i];
}
return ans;
}
}
void modify(int l, int r, int x) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) {//在一个快
a[i] += x;
sum[bel[i]] += x;
}
} else {
for (int i = l; i <= ed[bel[l]]; i++) { //左边
a[i] += x;
sum[bel[i]] += x;
}
for (int i = st[bel[r]]; i <= r; i++) { //右边
a[i] += x;
sum[bel[i]] += x;
}
int L = bel[l] + 1, R = bel[r] - 1;
for (int i = L; i <= R; i++) {
// sum[i] += sz[i] * x;
tag[i] += x;
}
}
}
void solve(int Case) {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
init(n);
for (int i = 1; i <= m; i++) {
int opt, l, r, c;
cin >> opt >> l >> r ;
if (opt == 1) {
cin >> c;
modify(l, r, c);
} else {
cout << query(l, r) << nline;
}
}
}
P2801 教主的魔法
区间加操作分块可以轻松完成,对于查询大于等于w的数字,不完整的块直接暴力跑,完整的块可以排序二分求;
首先预处理每个块并且排好序,对于每次修改不完整的块,单独进行一次排序,这样就保证块内始终是有序的;
const int N = 2000100, B = sqrt(N);
int block;
int st[B], ed[B], bel[N];
int sum[B], tag[B];
int a[N], sz[B];
int c[N];
void Sort(int k) {
int l = st[k], r = ed[k];
for (int i = l; i <= r; i++) c[i] = a[i];
sort(c + l, c + r + 1);
}
void init(int n) {
block = sqrt(n + 0.5);
for (int i = 1; i <= block; i++) {
st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
ed[i] = n / block * i;
}
ed[block] = n;
for (int i = 1; i <= block; i++) {
for (int j = st[i]; j <= ed[i]; j++) {
bel[j] = i;//编号
sum[i] += a[j];
}
sz[i] = ed[i] - st[i] + 1; //大小
}
for (int i = 1; i <= n; i++) c[i] = a[i];
for (int i = 1; i <= block; i++) {
Sort(i);
}
}
int query(int l, int r, int k) {
int ans = 0;
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) {
ans += (a[i] + tag[bel[i]] >= k);
}
return ans;
} else {
for (int i = l; i <= ed[bel[l]]; i++) {
ans += (a[i] + tag[bel[i]] >= k);
}
for (int i = st[bel[r]]; i <= r; i++) {
ans += (a[i] + tag[bel[i]]) >= k;
}
int L = bel[l] + 1, R = bel[r] - 1;
for (int i = L; i <= R; i++) {
//
ans += ed[i] - (lower_bound(c + st[i], c + ed[i] + 1, k - tag[i]) - c) + 1;
}
return ans;
}
}
void modify(int l, int r, int x) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) {//在一个快
a[i] += x;
sum[bel[i]] += x;
}
Sort(bel[l]);
} else {
for (int i = l; i <= ed[bel[l]]; i++) { //左边
a[i] += x;
sum[bel[i]] += x;
}
Sort(bel[l]);
for (int i = st[bel[r]]; i <= r; i++) { //右边
a[i] += x;
sum[bel[i]] += x;
}
Sort(bel[r]);
int L = bel[l] + 1, R = bel[r] - 1;
for (int i = L; i <= R; i++) {
// sum[i] += sz[i] * x;
tag[i] += x;
}
}
}
void solve(int Case) {
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
init(n);
for (int i = 1; i <= q; i++) {
int l, r, w;
char op[2];
cin >> op;
cin >> l >> r >> w;
if (op[0] == 'M') {
modify(l, r, w);
} else {
cout << query(l, r, w) << nline;
}
}
}
莫队
莫队算法具体操作是把查询区间按照左端点分块,块内右端点有序;
这样每次l能够移动的范围是sqrt(n),因为r是单调的,每个块最多跑n次,一共sqrt(n)个块,所以整体复杂度就是msqrt(n),m是查询次数
P1494 [国家集训队] 小 Z 的袜子
贡献区间内所有袜子种类x,sum(C(cnt[x],2))/C(len,2),len为长度,单独考虑每增加一个x类袜子,就多出cnt[x]种可能,每减少一个x,就减少cnt[x]种可能。
const int N = 500100;
int a[N];
int block;
struct T {
int l, r, id;
bool operator<(const T &t)const {
if (l / block != t.l / block) return l / block < t.l / block;
return r < t.r;
}
} q[N];
using PII = pair<int, int>;
PII ans[N];
int vis[N];
int cnt = 0;
void del(int x) {
vis[x]--;
cnt -= vis[x];
}
void add(int x) {
++vis[x];
cnt += vis[x] - 1;
}
// int ans[N];
void solve(int Case) {
int n, m;
cin >> n >> m;
block = sqrt(n + 0.5);
for (int i = 1; i <= n; i++) cin >> a[i];
// cin >> m;
for (int i = 1; i <= m; i++) {
auto &[l, r, id] = q[i];
cin >> l >> r;
id = i;
}
sort(q + 1, q + 1 + m);
int l = 1, r = 1;
cnt = 0;
vis[a[1]] = 1;
for (int i = 1; i <= m; i++) {
auto &[x, y, id] = q[i];
while (l > x) add(a[--l]);
while (r < y) add(a[++r]);
while (l < x) del(a[l++]);
while (r > y) del(a[r--]);
auto&[f, s] = ans[id];
f = cnt, s = (y - x + 1) * (y - x) / 2;
int g = __gcd(f, s);
if (x == y) {
f = 0, s = 1;
continue;
}
f /= g;
s /= g;
}
for (int i = 1; i <= m; i++) {
auto &[f, s] = ans[i];
cout << f << '/' << s << nline;
}
}
E. XOR and Favorite Number
考虑前缀异或和,s[i]^s[j-1]=k,则说明j~i的异或和等于k,可以统计前缀异或和的个数,然后类似上题
const int N = 2000100;
int a[N], s[N];
int vis[N];
int block;
int ans[N];
struct T {
int l, r, id;
bool operator<(const T &t) const {
if (l / block != t.l / block) return l / block < t.l / block;
return r < t.r;
}
} q[N];
int cnt = 0;
int n, m, k;
void del(int x) {
vis[x]--;
cnt -= vis[x ^ k];
}
void add(int x) {
cnt += vis[x ^ k];
vis[x]++;
}
void solve(int Case) {
cin >> n >> m >> k;
block = sqrt(n + 0.5);
for (int i = 1; i <= n; i++) cin >> a[i], s[i] = s[i - 1] ^ a[i];
for (int i = 1; i <= m; i++) {
auto &[l, r, id] = q[i];
cin >> l >> r;
id = i;
}
sort(q + 1, q + 1 + m);
int l = 1, r = 0;
vis[0]++;
for (int i = 1; i <= m; i++) {
auto [x, y, id] = q[i];
while (l > x) l--,add(s[l-1]);
while (r < y) add(s[++r]);
while (l < x) del(s[l-1]),l++;
while (r > y) del(s[r--]);
ans[id] = cnt;
}
for (int i = 1; i <= m; i++) cout << ans[i] << nline;
}
P4137 Rmq Problem / mex
判断每个数字是否出现过,这个过程可以通过分块来判断,如果一个块内数字全部出现则直接跳过,否则mex就在这个块内
配合莫队,整体复杂度m*sqrt(n)
const int N = 200010, B = sqrt(N);
int a[N];
int block;
int ans[N];
int f[B];
int sz[B], st[B], ed[B], bel[N];
int cnt[N];
void init(int n) {
block = sqrt(n + 0.5);
for (int i = 1; i <= block; i++) {
st[i] = n / block * (i - 1) + 1; // st[i]表示i号块的第一个元素的下标
ed[i] = n / block * i;
}
ed[block] = n;
for (int i = 1; i <= block; i++) {
for (int j = st[i]; j <= ed[i]; j++) {
bel[j] = i;//编号
}
sz[i] = ed[i] - st[i] + 1; //大小
}
}
struct T {
int l, r, id;
bool operator<(const T &t) const {
if (l / block != t.l / block) return l / block < t.l / block;
return r < t.r;
}
} q[N];
int n, m, k;
void del(int x) {
if (x > n + 1) return;
cnt[x]--;
if (!cnt[x]) {
f[bel[x]]--;
}
}
void add(int x) {
if (x > n + 1) return;
cnt[x]++;
if (cnt[x] == 1) {
f[bel[x]]++;
}
}
int query() {
for (int i = 1; i <= block; i++) {
if (f[i] == sz[i]) continue;
else {
for (int j = st[i]; j <= ed[i]; j++) {
if (cnt[j] == 0) return j;
}
}
}
return n + 2;
}
void solve(int Case) {
cin >> n >> m;
block = sqrt(n + 0.5);
for (int i = 1; i <= n; i++) cin >> a[i], a[i]++;
init(n+1);
for (yint i = 1; i <= m; i++) {
auto &[l, r, id] = q[i];
cin >> l >> r;
id = i;
}
sort(q + 1, q + 1 + m);
int l = 1, r = 0;
for (int i = 1; i <= m; i++) {
auto [x, y, id] = q[i];
while (l > x) add(a[--l]);
while (r < y) add(a[++r]);
while (l < x) del(a[l++]);
while (r > y) del(a[r--]);
ans[id] = query() - 1;
}
for (int i = 1; i <= m; i++) cout << ans[i] << nline;
}
带修莫队
多增加一维时间
复杂度证明参考
https://oi-wiki.org/misc/modifiable-mo-algo/
P1903 [国家集训队] 数颜色 / 维护队列
分别处理两个数组一个查询一个修改
块取n^(2/3)
排序1:左端点块2:右端点块3:修改时间
首先按照常规莫队调整区间
如果当前修改时间小于查询时修改时间,往后面修改
反之亦然,(注意在区间范围内的要对答案进行修改)
const int N = 1000100;
int vis[N], a[N], ans[N];
struct ch {
int p, c;
} c[N];
int block;
struct T {
int l, r, t, id;
bool operator<(const T &p) const {
if (l / block == p.l / block) {
if (r / block == p.r / block) {
return t < p.t;
}
return r / block < p.r / block;
}
return l / block < p.l / block;
}
} q[N];
int res = 0;
void add(int x) {
if (++vis[x] == 1) res++;
}
void del(int x) {
if (--vis[x] == 0) res--;
}
void modify(int qt, int ct) {//当前查询区间,修改时间
if (c[ct].p >= q[qt].l and c[ct].p <= q[qt].r) {
del(a[c[ct].p]);
add(c[ct].c);
}
swap(a[c[ct].p], c[ct].c);//交换,方便回退
}
int l = 1, r = 0;
void solve(int Case) {
int n, m;
cin >> n >> m;
block = pow(n, 0.66666);
for (int i = 1; i <= n; i++) cin >> a[i];
int qcnt = 0, ccnt = 0;
char op[2];
for (int i = 1; i <= m; i++) {
cin >> op;
int l, r;
if (op[0] == 'Q') {
cin >> l >> r;
q[++qcnt] = {l, r, ccnt, qcnt};
} else {
int pos, col;
cin >> pos >> col;
c[++ccnt] = {pos, col};
}
}
sort(q + 1, q + 1 + qcnt);
int now = 0;
for (int i = 1; i <= qcnt; i++) {
auto [x, y, t, id] = q[i];
while (l > x) add(a[--l]);
while (r < y) add(a[++r]);
while (l < x) del(a[l++]);
while (r > y) del(a[r--]);
while (now < t) modify(i, ++now);
while (now > t) modify(i, now--);
ans[id] = res;
}
for (int i = 1; i <= qcnt; i++) {
cout << ans[i] << nline;
}
}
本文参考oiwiki
https://oi-wiki.org/ds/decompose/
https://oi-wiki.org/ds/block-array/