文章目录
莫队
适用范围
离线,多次区间查询,查询一些线段树之类数据结构做不了的事情(区间第一个未出现的自然数、区间不同的数个数……)。还有一些特性,比如:区间扩大或缩小一个位置要比较快,要能分块。
思路
首先我们考虑,假如我们已经知道了 的答案,如何转移到另一个询问 。需要更新 和 两个区间。搞得不好的话,很显然,更新时间是会退化到 的。那么我们要做的就是尽量减少这两段区间的长度。
运用了分块的思想。排序来尽量减少重复的计算。
具体理解请看例题。
例题
【bzoj2038】小z的袜子
来源:bzoj2038
题意:给出序列,每个询问求一个区间内等概率随机取两个数,有多大概率两数相等。
那我们分三步思考:
一、单个询问
对于一个询问,总可能数:
设每种颜色的袜子有 只,那么:
二、递推
如何从上一个状态转移到当前状态呢?假设转移后加入了一个颜色为 的袜子,加上后这种颜色的袜子总共有 只。那么:
所以:
边界:
这样指针移动的同时就可以统计答案了。
三、排序
分块后,先按左端点属于哪个块,再按右端点,双关键字排序。
四、分块
设块大小为m。
右指针最多 ,因为对于每一块,右指针都最多从最左到最右遍历一遍序列。
左指针最多 ,因为对于每一个询问,他的左端点距离排在上一个的询问最多有一块的长度。
所以 m = sqrt(n)时复杂度最小。
因为是第一道例题所以贴一个代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e4+10;
int n, m, q, c[N];
struct NODE{
int l, r, bel, id;
ll ans;
}a[N];
int cnt[N];
ll out[N][2];
bool cmp(NODE x, NODE y){
return x.bel == y.bel ? x.r < y.r : x.bel < y.bel;
}
int main()
{
scanf("%d%d", &n, &q);
m = sqrt(n);
for (int i = 1; i <= n; ++ i)
scanf("%d", c+i);
for (int i = 1; i <= q; ++ i){
scanf("%d%d", &a[i].l, &a[i].r);
a[i].bel = a[i].l/m;
a[i].id = i;
}
sort(a+1, a+q+1, cmp);
{
int i = 1, j;
while (i <= q){
int l = 1, r = 0;
ll an = 0;
memset(cnt, 0, sizeof(cnt));
for (j = i; a[i].bel == a[j].bel; ++ j){
while (r < a[j].r)
++ r, an += cnt[c[r]], ++ cnt[c[r]];
while (l < a[j].l)
-- cnt[c[l]], an -= cnt[c[l]], ++ l;
while (l > a[j].l)
-- l, an += cnt[c[l]], ++ cnt[c[l]];
a[j].ans = an;
}
i = j;
}
}
for (int i = 1; i <= q; ++ i)
out[a[i].id][0] = a[i].ans, out[a[i].id][1] = 1ll*(a[i].r-a[i].l+1)*(a[i].r-a[i].l)/2;
for (int i = 1; i <= q; ++ i){
ll x = out[i][0], y = out[i][1], d = __gcd(x, y);
printf("%lld/%lld\n", x/d, y/d);
}
return 0;
}
看似只是排了一遍序,但是成功将复杂度由 降到了 ,好腻害呀。
带修莫队
思路
唯一的变化就是增加了时间这一个维度。
加上时间之后,询问变成 的三元组。然后的思路就和之前一模一样啦,在一块内让某一维的指针只会单向移动,然后其他两维在块内乱跳。
例题
【bzoj2120】数颜色
来源:bzoj2120
题意:一个序列,有修改操作,询问一段区间有多少个不同的数。
模板题。思路上面讲了,都是套路。
假如序列长度和询问个数同阶的话,复杂度最低是 。
最简单的例题放上代码:
p.s.莫队写起来很短的而且代码思路很清晰。下面的代码因为头上一大坨加上离散化才这么长的
#include<bits/stdc++.h>
#define rep(i, a, b) for (int i = a, ub##i = b; i <= ub##i; ++ i)
#define per(i, a, b) for (int i = a, ub##i = b; i >= ub##i; -- i)
template<class T>void chkMax(T &x, T y){if (x < y) x = y;}
template<class T>void chkMin(T &x, T y){if (x > y) x = y;}
#define rdi read<int>
#define rdl read<long long>
template<typename T> inline T read()
{
T x = 0, fh = 1;
char c = getchar();
while (c < '0' || c > '9'){if (c == '-') fh = -1; c = getchar();}
while (c >= '0' && c <= '9') x = (x<<3)+(x<<1)+c-'0', c = getchar();
return x*fh;
}
using namespace std;
const int N = 5e4+10;
const int C = N<<1;
int n, t, rn, qn, m, c[N], cnt[C], an, ans[N], ls[C], lsn;
struct R{int t, pos, pre, now, bt;}r[N];
struct Q{int t, id, l, r, bt, bl;}q[N];
bool cmp(Q x, Q y){
return x.bt == y.bt ? (x.bl == y.bl ? x.r < y.r : x.bl < y.bl) : x.bt < y.bt;
}
void update(int col, int inc)
{
cnt[col] += inc;
if (inc == 1 && cnt[col] == 1) ++ an;
if (inc == -1 && cnt[col] == 0) -- an;
}
int main()
{
n = rdi(); t = rdi();
m = pow(sqrt(1.0*n*t), 2.0/3);
lsn = 0;
rep(i, 1, n)
c[i] = rdi(), ls[++ lsn] = c[i];
rn = qn = 0;
rep(i, 1, t){
char s[1]; int x, y;
scanf("%s", s);
x = rdi(); y = rdi();
if (s[0] == 'R')
r[++ rn] = (R){i, x, 0, y, i/m}, ls[++ lsn] = y;
else
q[++ qn] = (Q){i, qn, x, y, i/m, x/m};
}
sort(ls+1, ls+lsn+1);
lsn = unique(ls+1, ls+lsn+1)-(ls+1);
rep(i, 1, n)
c[i] = lower_bound(ls+1, ls+lsn+1, c[i])-ls;
rep(i, 1, rn)
r[i].now = lower_bound(ls+1, ls+lsn+1, r[i].now)-ls;
rep(i, 1, rn)
r[i].pre = c[r[i].pos], c[r[i].pos] = r[i].now;
per(i, rn, 1)
c[r[i].pos] = r[i].pre;
sort(q+1, q+qn+1, cmp);
{
int i = 1, j, k = 0, l, _r;
while (i <= qn){
l = 1; _r = 0;
memset(cnt, 0, sizeof(cnt)); an = 0;
per(o, k, 1)
c[r[o].pos] = r[o].pre;
k = 0;
for (j = i; q[j].bt == q[i].bt && q[j].bl == q[i].bl && j <= qn; ++ j){
while (q[j].r > _r) ++ _r, update(c[_r], 1);
while (q[j].l > l) update(c[l], -1), ++ l;
while (q[j].l < l) -- l, update(c[l], 1);
while (k > 0 && q[j].t < r[k].t){
if (r[k].pos >= l && r[k].pos <= _r)
update(r[k].pre, 1), update(r[k].now, -1);
c[r[k].pos] = r[k].pre;
-- k;
}
while (k < rn && q[j].t > r[k+1].t){
++ k;
if (r[k].pos >= l && r[k].pos <= _r)
update(r[k].pre, -1), update(r[k].now, 1);
c[r[k].pos] = r[k].now;
}
ans[q[j].id] = an;
}
i = j;
}
}
rep(i, 1, qn)
printf("%d\n", ans[i]);
return 0;
}
【CF940E】Machine Learning
div2最后一题居然是莫队模板,太可怕了。
套路,再用线段树或者树状数组维护一下 就好了。
树上莫队
思路
还是套路,只不过被分块的东西从线性的序列变成了一棵树。
例题
【bzoj1086】王室联邦
来源:bzoj1086
题意:
让你把树分成大小为 的块,要求每块所有点到这一块的关键点都只经过这一块的点。
思路:
一遍dfs,维护一个栈,dfs一个点时先记录初始栈顶高度,每dfs完当前节点的一棵子树就判断栈内(相对于刚开始dfs时)新增节点的数量是否 ,是则将栈内所有新增点分为同一块,核心点为当前dfs的点,当前节点结束dfs时将当前节点入栈,整个dfs结束后将栈内所有剩余节点归入已经分好的最后一个块。
这也是树上莫队所需要的分块方法,这样使得每一块内任意两点间距离不超过 。证明这个博客讲的很清楚ouuan的博客园。
【bzoj4129】Haruna’s Breakfast
来源:bzoj4129
树上带修莫队维护 值。前面带修莫队的例题Machine Learning的进化版。
代码:
【WC2013】糖果公园
和上一题几乎一样,只是维护的东西变成了每一种糖果已经有了几个。
总结
莫队是一种很套路的方法,假如出题人想让你写,那你一眼就能看出来。
更重要的是学习分块的思想。反正我是这么觉得的。