分块思想基础莫队
Posted 指引盗寇入太行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分块思想基础莫队相关的知识,希望对你有一定的参考价值。
分块
将数组分成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;
本文参考oiwiki
https://oi-wiki.org/ds/decompose/
https://oi-wiki.org/ds/block-array/
[学习-思考-探究]莫队算法 曼哈顿最小生成树与分块区间询问算法-4
在线区间询问算法(奇妙算法)
这是最神奇的算法,不仅简单还可以实现在线询问+修改。
考虑基础算法中的优化。
如果我们把整个区间分成$n^{\\frac{1}{3}}$块,那么就可以记录任意两块之间的状态啦!
然后只要套用基础算法当中的操作就可以啦!
总空间复杂度和时间复杂度都是$O(n^{\\frac{3}{4}})$,还是相当好的。
下面是例题:
小Y的房间
标程就不放出来啦。
如有不懂或有兴趣进行讨论,请联系QQ2502669375!
以上是关于分块思想基础莫队的主要内容,如果未能解决你的问题,请参考以下文章