莫队总结 题解

莫队介绍

莫队,就是通过离线,将询问排序,然后依次将上一个询问暴力移动到下一个的算法。(通常是区间询问)
首先,找一种合适的顺序处理,否则显然会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]大数]

[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]糖果公园

[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;
}
原文地址:https://www.cnblogs.com/lnzwz/p/11291260.html