莫队介绍
莫队,就是通过离线,将询问排序,然后依次将上一个询问暴力移动到下一个的算法。(通常是区间询问)
首先,找一种合适的顺序处理,否则显然会T。
如果只按照左端点排序,那么右端点可能移动次数很多。
折中一下,将序列以(O(sqrt{n}))分块,按左端点所在块为第一关键字,右端点为第二关键字排序。
这样,移动次数就是(O(nsqrt{n}))的了。
证明:左端点:块内每次不超过(O(sqrt{n})),块外总和为(O(n))。右端点:每个块最多从1~n,总共不超过(O(nsqrt{n}))。
优化:
1.根据经验,数列长n,询问为m时,块大小(B=frac{n}{sqrt{m*frac{2}{3}}})时较优。
2.我们发现:在第一个块时,右端点从左到右,在第二个块时,右端点还是从左到右,中间需要把右端点移回来,很费时间。
使用奇偶块优化:
int cmp(const void*a2,const void*b2)
{
SXw a=*(SXw*)a2,b=*(SXw*)b2;
if(ku[a.l]==ku[b.l])
return ku[a.l]&1?a.r-b.r:b.r-a.r;
return a.l-b.l;
}
能节省约1/3的时间。
带修改莫队:
普通的莫队只能解决不待修改的问题。
扩展一下也能支持修改。
普通的莫队有两个指针,加一个指针表示时间即可。
转移时先考虑时间序。
注意块大小为(O(n^{2/3})),排序时左端点所在块为第一关键字,右端点所在块为第二关键字,时间序为第三关键字。
似乎也可以用奇偶块优化。
代码:
int cmp(const void*a2,const void*b2)
{
SXw a=*(SXw*)a2,b=*(SXw*)b2;
if(ku[a.x]!=ku[b.x])
return ku[a.x]-ku[b.x];
if(ku[a.y]!=ku[b.y])
return ku[a.x]&1?ku[a.y]-ku[b.y]:ku[b.y]-ku[a.y];
return ku[a.y]&1?a.t-b.t:b.t-a.t;
}
树上莫队:
普通莫队处理序列的操作,但莫队也能处理树上问题。
树分块:
(参考课件)
设块的大小是 B 。
进行 dfs,搜到某个点时,如果它的前 k 个儿子的剩余节点数之和 >=B 了,
就把这些点作为一个新的块(注意,不算当前节点),
存储这些节点可以用一个栈搞。
扫描完所有点之后,如果还有的点没有被分块,就把这些点连同当前搜到的点一起作为剩余节点返回到上一层。
搜索结束之后可能还会有点没被分块,把这些点加到最后一个块里就行了。
指针的转移:
指针转移没有序列上那样简单。
(参考课件)
我们令 S(v,u) 表示 v 到 u 的路径
那么 S(v,u)=S(u,root) xor S(v,root) xor {lca(u,v)}
xor 指的是对称差,就相当于去重。
我们再令 T(u,v)=S(u,root)xor S(v,root)
当从 T(u,v) 更新到 T(u',v) 时
我们有:
T(u',v)=S(root,u') xor S(root,v)
T(u',v) xor T(u,v)
=S(root,u') xor S(root,v) xor S(u,root) xor S(v,root)
=S(root,u') xor S(root,u) =T(u',u).
lca:每次处理询问的时候更新上,处理完更新回来就行了。
从 u,v 更新到 u',v':更新两次就好了。
复杂度也是(O(nsqrt{n}))。
例题
1、[[HNOI2016]大数]
使用类似hash的方法提取子串。
l+1~r的值为(S(r)-S(l)*10^{r-l})。
是7的倍数即(S(r)mod P=S(l)*10^{r-l} mod P)。
两边乘以(10^{n-r}),得(S(r)*10^{n-r} mod P=S(l)*10^{n-l} mod P)。
然后就是区间(S(i)*10^{n-i})出现次数平方和了。
注意:当P等于2或5时,要特判,考虑个位即可。
代码:
#include < stdio.h > #include < string.h > #include < stdlib.h > #include < math.h > #define ll long long char zf[100010];
int S[100010],
mi[100010],
sz[100010];
struct SPx {
int z,
i;
};
SPx px[100010];
int cmp(const void * a, const void * b) {
return ((SPx * ) a) - >z - ((SPx * ) b) - >z;
}
int sl[100010],
ku[100010];
ll ans = 0;
void add(int i) {
ans = ans + sl[sz[i]];
sl[sz[i]] += 1;
}
void del(int i) {
ans = ans - sl[sz[i]] + 1;
sl[sz[i]] -= 1;
}
struct SXw {
int l,
r,
i;
};
SXw xw[100010];
int cmp2(const void * a2, const void * b2) {
SXw a = *(SXw * ) a2,
b = *(SXw * ) b2;
if (ku[a.l] == ku[b.l]) return ku[a.l] & 1 ? a.r - b.r: b.r - a.r;
return a.l - b.l;
}
ll jg[100010];
ll su1[100010],
su2[100010];
int main() {
int p,
n,
m,
k = 0;
scanf("%d%s%d", &p, zf, &m);
n = strlen(zf);
if (p == 2 || p == 5) {
for (int i = 1; i <= n; i++) {
su1[i] = su1[i - 1];
su2[i] = su2[i - 1];
if ((zf[i - 1] - '0') % p == 0) {
su1[i] += i;
su2[i] += 1;
}
}
for (int i = 0; i < m; i++) {
int l,
r;
scanf("%d%d", &l, &r);
printf("%lld
", su1[r] - su1[l - 1] - (l - 1) * (su2[r] - su2[l - 1]));
}
return 0;
}
int si = n / sqrt(m * 2 / 3);
if (si == 0) si = 1;
for (int i = 0; i <= n; i++) ku[i] = i / si;
for (int i = 1; i <= n; i++) S[i] = (S[i - 1] * 10ll + zf[i - 1] - '0') % p;
mi[0] = 1;
for (int i = 1; i <= n; i++) mi[i] = mi[i - 1] * 10ll % p;
for (int i = 0; i <= n; i++) sz[i] = 1ll * S[i] * mi[n - i] % p;
for (int i = 0; i <= n; i++) {
px[i].i = i;
px[i].z = sz[i];
}
qsort(px, n + 1, sizeof(SPx), cmp);
for (int i = 0; i <= n; i++) {
if (i > 0 && px[i].z != px[i - 1].z) k += 1;
sz[px[i].i] = k;
}
for (int i = 0; i < m; i++) {
int l,
r;
scanf("%d%d", &l, &r);
xw[i].l = l - 1;
xw[i].r = r;
xw[i].i = i;
}
qsort(xw, m, sizeof(SXw), cmp2);
int tl = 0,
tr = -1;
for (int i = 0; i < m; i++) {
int l = xw[i].l,
r = xw[i].r;
while (tl - 1 >= l) {
add(tl - 1);
tl -= 1;
}
while (tr + 1 <= r) {
add(tr + 1);
tr += 1;
}
while (tl + 1 <= l) {
del(tl);
tl += 1;
}
while (tr - 1 >= r) {
del(tr);
tr -= 1;
}
jg[xw[i].i] = ans;
}
for (int i = 0; i < m; i++) printf("%lld
", jg[i]);
return 0;
}
2、 Gty的二逼妹子序列
Gty的二逼妹子序列
CDQ被卡空间……
考虑莫队:类似HH的项链,直接维护单点修改,区间求和。
需要(O(nsqrt{n}))次修改,然而只有(O(m))次询问。
如果树状数组的话十分浪费。
考虑均衡下复杂度:使用一种修改快,询问慢的方法维护。
分块即可。(O(1))修改,(O(sqrt{n}))查询。
注意空间。
代码:
#include < stdio.h >
#include < math.h >
#include < algorithm >
using namespace std;
int sz[100002],sl[100002],si;
struct SXw {
int l,r,a,b,i;
SXw() {}
SXw(int L, int R, int A, int B, int I) {
l = L; r = R; a = A; b = B; i = I;
}
};
SXw xw[1000002];
bool operator < (SXw a, SXw b) {
if (a.l / si == b.l / si) return (a.l / si) & 1 ? a.r < b.r: b.r < a.r;
return a.l < b.l;
}
int st[1002],en[1002],he[1002],wz[100002],dx,ks;
int ans[1000002];
void yucl(int n) {
dx = int(pow(n, 0.47) + 0.5);
while (1) {
st[ks] = (ks == 0 ? 1 : en[ks - 1] + 1);
en[ks] = st[ks] + dx - 1;
if (en[ks] > n) en[ks] = n;
ks += 1;
if (en[ks - 1] == n) break;
}
for (int i = 0; i < ks; i++) {
for (int j = st[i]; j <= en[i]; j++) wz[j] = i;
}
}
void addk(int i, int x) {
he[wz[i]] += x;
}
int blsum(int l, int r) {
int rt = 0;
for (int i = l; i <= r; i++) rt += (sl[i] > 0);
return rt;
}
int sum(int l, int r) {
if (wz[l] == wz[r]) return blsum(l, r);
int x = wz[l] + 1,
y = wz[r] - 1,
rt = 0;
for (int i = x; i <= y; i++) rt += he[i];
rt += blsum(l, st[x] - 1) + blsum(en[y] + 1, r);
return rt;
}
void add(int x) {
sl[x] += 1;
if (sl[x] == 1) addk(x, 1);
}
void del(int x) {
sl[x] -= 1;
if (sl[x] == 0) addk(x, -1);
}
int main() {
int n,m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &sz[i]);
si = n / sqrt(m * 2 / 3);
if (si == 0) si = 1;
for (int i = 0; i < m; i++) {
int l,r,a,b;
scanf("%d%d%d%d", &l, &r, &a, &b);
xw[i] = SXw(l, r, a, b, i);
}
sort(xw, xw + m);
yucl(n);
int tl = 1,tr = 0;
for (int i = 0; i < m; i++) {
int l = xw[i].l,r = xw[i].r;
while (tl - 1 >= l) {
tl -= 1;
add(sz[tl]);
}
while (tr + 1 <= r) {
tr += 1;
add(sz[tr]);
}
while (tl + 1 <= l) {
del(sz[tl]);
tl += 1;
}
while (tr - 1 >= r) {
del(sz[tr]);
tr -= 1;
}
ans[xw[i].i] = sum(xw[i].a, xw[i].b);
}
for (int i = 0; i < m; i++) printf("%d
", ans[i]);
return 0;
}
3、[WC2013]糖果公园
就是带修改树上莫队。
注意卡常。
代码:
#include < stdio.h >
#include < math.h >
#include < stdlib.h >
#define ll long long
#define re register int fr[100010],ne[200010],v[200010],bs = 0;
void addb(int a, int b) {
v[bs] = b;
ne[bs] = fr[a];
fr[a] = bs++;
}
int st[100010],tp = 0,B,ku[100010],ks = 0;
void dfs0(int u, int f) {
for (int i = fr[u]; i != -1; i = ne[i]) {
if (v[i] == f) continue;
dfs0(v[i], u);
if (tp >= B) {
for (int j = 0; j < tp; j++) ku[st[j]] = ks;
ks += 1;
tp = 0;
}
}
st[tp++] = u;
}
int fa[100010],son[100010],top[100010],sd[100010];
int dfs1(int u, int f) {
int he = 1,ma = -1;
son[u] = -1;sd[u] = sd[f] + 1;
fa[u] = f;
for (int i = fr[u]; i != -1; i = ne[i]) {
if (v[i] == f) continue;
int rt = dfs1(v[i], u);
he += rt;
if (rt > ma) {
ma = rt;
son[u] = v[i];
}
}
return he;
}
void dfs2(int u, int f, int tp) {
top[u] = tp;
if (son[u] == -1) return;
dfs2(son[u], u, tp);
for (int i = fr[u]; i != -1; i = ne[i]) {
if (v[i] != f && v[i] != son[u]) dfs2(v[i], u, v[i]);
}
}
int getlca(int x, int y) {
while (top[x] != top[y]) {
if (sd[top[x]] > sd[top[y]]) x = fa[top[x]];
else y = fa[top[y]];
}
if (sd[x] < sd[y]) return x;
else return y;
}
int sl[100010],co[100010];
ll he = 0,ans[100010];
int V[100010],W[100010];
bool bk[100010];
void add(re int x) {
sl[x] += 1;
he += 1ll * W[sl[x]] * V[x];
}
void del(re int x) {
he -= 1ll * W[sl[x]] * V[x];
sl[x] -= 1;
}
void xiug(re int x, re int y) {
if (bk[x]) {
del(co[x]);
add(y);
}
co[x] = y;
}
void Xord(re int x) {
if (bk[x]) del(co[x]);
else add(co[x]);
bk[x] = !bk[x];
}
void Xor(re int x, re int y) {
re int lc = getlca(x, y);
while (x != lc) {
Xord(x);
x = fa[x];
}
while (y != lc) {
Xord(y);
y = fa[y];
}
}
void move(re int a, re int b, re int x, re int y) {
re int lc1 = getlca(a, b);
re int lc2 = getlca(x, y);
Xord(lc1);
Xor(a, x);
Xor(b, y);
Xord(lc2);
}
struct SXw {
int x,y,i,t;
SXw() {}
SXw(int X, int Y, int I, int T) {
x = X;y = Y;
i = I;t = T;
}
};
SXw xw[100010];
int cmp(const void * a2, const void * b2) {
SXw a = *(SXw * ) a2,
b = *(SXw * ) b2;
if (ku[a.x] != ku[b.x]) return ku[a.x] - ku[b.x];
if (ku[a.y] != ku[b.y]) return ku[a.x] & 1 ? ku[a.y] - ku[b.y] : ku[b.y] - ku[a.y];
return ku[a.y] & 1 ? a.t - b.t: b.t - a.t;
}
int wz[100010],ol[100010],xg[100010],tm[100010];
int main() {
int n,m,q,
k = 0,s = 0;
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= m; i++) scanf("%d", &V[i]);
for (int i = 1; i <= n; i++) scanf("%d", &W[i]);
for (int i = 1; i <= n; i++) fr[i] = -1;
for (int i = 0; i < n - 1; i++) {
int a,b;
scanf("%d%d", &a, &b);
addb(a, b);
addb(b, a);
}
for (int i = 1; i <= n; i++) {
scanf("%d", &co[i]);
tm[i] = co[i];
}
B = int(pow(n, 0.62) + 0.5);
dfs0(1, 0);dfs1(1, 0),
dfs2(1, 0, 1);
for (int i = 0; i < tp; i++) ku[st[i]] = ks;
ks += 1;
for (int i = 0; i < q; i++) {
int ty,x,y;
scanf("%d%d%d", &ty, &x, &y);
if (ty == 0) {
k += 1;
wz[k] = x;
ol[k] = tm[x];
xg[k] = tm[x] = y;
} else {
int t = s;
xw[s++] = SXw(x, y, t, k);
}
}
qsort(xw, s, sizeof(SXw), cmp);
re int lx = 1,ly = 1,lt = 0;
Xord(1);
for (int i = 0; i < s; i++) {
while (lt + 1 <= xw[i].t) {
lt += 1;
xiug(wz[lt], xg[lt]);
}
while (lt - 1 >= xw[i].t) {
xiug(wz[lt], ol[lt]);
lt -= 1;
}
move(lx, ly, xw[i].x, xw[i].y);
lx = xw[i].x;
ly = xw[i].y;
ans[xw[i].i] = he;
}
for (int i = 0; i < s; i++) printf("%lld
", ans[i]);
return 0;
}