算法学习笔记:再谈莫队二次离线

算法学习笔记:再谈莫队二次离线

1. 前言

莫队二次离线,是一种莫队,由 lxl 发明,专门用来处理莫队中转移不是 O ( 1 ) O(1) O(1),但是可以前缀和拆分的问题。

如果您看到了标题,会注意到 再谈 二字。

其实作者之前写过一篇莫队二次离线的博客,但是现在看来感觉理解太浅了,于是决定重新写一篇。

在学习之前,请先确保对莫队有一定的了解度,包括但不限于普通莫队及其优化。

如果您不知道普通莫队是什么,可以看一看我的 这篇博文

2. 模板

模板题:P4887 【模板】莫队二次离线(第十四分块(前体))

首先考虑莫队 废话

一种思路就是直接利用普通莫队,每一次移动指针的时候 O ( C 14 k ) O(C_{14}^k) O(C14k) 的复杂度来转移,但是这显然会超时。

那么考虑如何优化复杂度。

接下来设 f ( x , [ l , r ] ) f(x,[l,r]) f(x,[l,r]) 表示 x x x 对区间 [ l , r ] [l,r] [l,r] 的贡献, g ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) g([l_1,r_1],[l_2,r_2]) g([l1,r1],[l2,r2]) 表示 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] 中的每一个数对 [ l 2 , r 2 ] [l_2,r_2] [l2,r2] 的贡献,也就是 ∑ i = l 1 r 1 f ( a i , [ l 2 , r 2 ] ) \sum_{i=l_1}^{r_1}f(a_i,[l_2,r_2]) i=l1r1f(ai,[l2,r2])

那么在转移的时候,我们对式子做一个变形:

f ( x , [ l , r ] ) = f ( x , [ 1 , r ] ) − f ( x , [ 1 , l − 1 ] ) f(x,[l,r])=f(x,[1,r]) - f(x,[1,l-1]) f(x,[l,r])=f(x,[1,r])f(x,[1,l1])

也就是可以差分。

那么如果记 h ( [ l , r ] ) h([l,r]) h([l,r]) 表示区间 [ l , r ] [l,r] [l,r] 的答案,那么仿照上面做一遍差分:

h ( [ l , r ] ) = h ( [ 1 , r ] ) − h ( [ 1 , l − 1 ] ) − g ( [ 1 , l − 1 ] , [ l , r ] ) h([l,r])=h([1,r])-h([1,l-1])-g([1,l-1],[l,r]) h([l,r])=h([1,r])h([1,l1])g([1,l1],[l,r])

于是我们发现此时的结果分为两部分:形如 h ( [ 1 , k ] ) h([1,k]) h([1,k]) 的和形如 g ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) g([l_1,r_1],[l_2,r_2]) g([l1,r1],[l2,r2]) 的。

那么形如 h ( [ 1 , k ] ) h([1,k]) h([1,k]) 的可以采用一遍预处理与一遍莫队求出来,这样就解决了第一部分。

但是 g ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) g([l_1,r_1],[l_2,r_2]) g([l1,r1],[l2,r2]) 要怎么办啊?

看一下前面给出的式子: g ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) = ∑ i = l 1 r 1 f ( a i , [ l 2 , r 2 ] ) g([l_1,r_1],[l_2,r_2])=\sum_{i=l_1}^{r_1}f(a_i,[l_2,r_2]) g([l1,r1],[l2,r2])=i=l1r1f(ai,[l2,r2])

这与莫队指针移动的连续性恰好吻合。

然后如果您学过扫描线,您还可以发现所有形如 g ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) g([l_1,r_1],[l_2,r_2]) g([l1,r1],[l2,r2]) 可以利用扫描线解决。

那么于是我们先用一次莫队算出形如 h ( [ 1 , k ] ) h([1,k]) h([1,k]) 的答案以及处理出所有形如 g ( [ l 1 , r 1 ] , [ l 2 , r 2 ] ) g([l_1,r_1],[l_2,r_2]) g([l1,r1],[l2,r2]) 的询问,然后一遍扫描线解决答案。

然后最后的答案统计在 a n s ans ans 数组内。

于是您连样例都过不去。

为什么?上面的过程有问题吗?

其实上面的过程没问题,关键的一点就是在执行上述过程的时候实际上 a n s ans ans 记录的是答案的变化量,最后还要做一遍前缀和才能通过。

友情提醒:考虑到代码里面使用了先进的 STL:tuple,因此请采用 C++17 及以上编译。

当然可以写个结构体

代码:

/*
========= Plozia =========
	Author:Plozia
	Problem:P4887 【模板】莫队二次离线(第十四分块(前体))
	Date:本代码书写于 2020/12/16
========= Plozia =========
*/

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int MAXN = 1e5 + 10;
int n, m, k, a[MAXN], t[MAXN], p[MAXN], block, ys[MAXN];
LL ans[MAXN];
struct node
{
	int l, r, id;
	LL ans;
}q[MAXN];
vector <int> b;
vector < tuple <int, int, int, int> > v[MAXN];

int read()
{
	int sum = 0; char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') {sum = (sum << 3) + (sum << 1) + (ch ^ 48); ch = getchar();}
	return sum;
}

bool cmp(const node &fir, const node &sec)
{
	if (ys[fir.l] ^ ys[sec.l]) return ys[fir.l] < ys[sec.l];
	return fir.r < sec.r;
}

int Count(int x)
{
	int sum = 0;
	for (; x; x >>= 1)
		if (x & 1) sum++;
	return sum;
}//统计位数

int main()
{
	n = read(); m = read(); k = read(); block = ceil(n / sqrt(m));
	if (k > 14)
	{
		for (int i = 1; i <= m; ++i) printf("0\n");
		return 0;
	}//坑
	for (int i = 1; i <= n; ++i) {a[i] = read(); ys[i] = (i - 1) / block + 1;}
	for (int i = 1; i <= m; ++i) {q[i].l = read(); q[i].r = read(); q[i].id = i;}
	sort(q + 1, q + m + 1, cmp);
	for (int i = 0; i < 16384; ++i)
		if (Count(i) == k) b.push_back(i);
	for (int i = 1; i <= n; ++i)
	{
		p[i] = t[a[i]];
		for (int j = 0; j < b.size(); ++j) t[a[i] ^ b[j]]++;
	}//预处理
	memset(t, 0, sizeof(t));
	int l = 1, r = 0;
	for (int i = 1; i <= m; ++i)
	{
		if (l > q[i].l) v[r].emplace_back(q[i].l, l - 1, i, 1);
		while (l > q[i].l) q[i].ans -= p[--l];
		if (r < q[i].r) v[l - 1].emplace_back(r + 1, q[i].r, i, -1);
		while (r < q[i].r) q[i].ans += p[++r];
		if (l < q[i].l) v[r].emplace_back(l, q[i].l - 1, i, -1);
		while (l < q[i].l) q[i].ans += p[l++];
		if (r > q[i].r) v[l - 1].emplace_back(q[i].r + 1, r, i, 1);
		while (r > q[i].r) q[i].ans -= p[r--];
	}//莫队,处理 h([1,k]) 答案以及 g([l1,r1],[l2,r2]) 的询问
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 0; j < b.size(); ++j) ++t[a[i] ^ b[j]];
		for (int j = 0; j < v[i].size(); ++j)
		{
            tuple x = v[i][j];
			for (int zzh = get<0>(x); zzh <= get<1>(x); ++zzh)
			{
				if (zzh <= i && k == 0) q[get<2>(x)].ans += get<3>(x) * (t[a[zzh]] - 1);
				else q[get<2>(x)].ans += get<3>(x) * t[a[zzh]];
			}
		}
	}//扫描线
	for (int i = 1; i <= m; ++i) q[i].ans += q[i-1].ans;//注意答案要差分
	for (int i = 1; i <= m; ++i) ans[q[i].id] = q[i].ans;//重新还原答案
	for (int i = 1; i <= m; ++i) printf("%lld\n", ans[i]);
	return 0;
}

3. 总结

莫队二次离线的式子: h ( [ l , r ] ) = h ( [ 1 , r ] ) − h ( [ 1 , l − 1 ] ) − g ( [ 1 , l − 1 ] , [ l , r ] ) h([l,r])=h([1,r])-h([1,l-1])-g([1,l-1],[l,r]) h([l,r])=h([1,r])h([1,l1])g([1,l1],[l,r])

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值