[Ynoi2018] GOSICK

莫队二离好题。

Description

给定长度为 \(n\) 的序列 \(a\)\(m\) 次询问,每次询问给定区间 \([l,\ r]\) ,求二元组 \((i,\ j)\) 满足 \(l \leq i,\ j \leq r\)\(a_i\)\(a_j\) 的倍数。

Analysis

这玩意似乎不太支持区间合并,或者说复杂度很不好看,考虑莫队能不能做。

又因为这个二元组仅针对两个数而言,可以差分,所以现在就要来分析怎么快速算 \([1,\ l]\)\(r + 1\)\([1,\ r]\)\(r + 1\) 的答案。

相当于我们要维护 \(a_{r + 1}\) 在前缀中的倍数和因数数量

Solution

因数好说,因为每个数的因数个数是 根号级别的,均摊下来是能直接 \(O(\sqrt{n})\) 预处理的,之后就可以 \(O(1)\) 查询,在莫队里面就能接受了。但是倍数复杂度能构造成 \(O(n)\) 处理,然后被莽起干爆。

(然后我就又不会了)

复杂度瓶颈的话主要是因为数比较小的话倍数数量级很大,但是稍微大一点的就问题不大,所以考虑根号分治。

令阈值为 \(b\)\(\geq b\) 的数直接暴力找倍数,因为倍数个数是 \(\frac{n}{b}\) 个的,时间复杂度 \(O(n \cdot \frac{n}{b})\)\(< b\) 的数因为倍数数量太多,并且好像也没什么方法能优化时间,然后直接冲过去。

这时候就一定要避其锋芒。换个角度想想,我们想要的是在莫队里面能做到 \(O(1)\) 查询,但是这样子需要预处理,牺牲了很多时间,所以如果能不预处理还能 \(O(1)\) 查询就好了。

相当于现在对于一个节点 \(l\) 我们每次需要计算它的答案的时候用的是预处理的 \(ans\) ,所以我们想把所有 \(l\) 的询问一步到位。

考虑二次离线,把所有相同点的操作留下来堆一起,这样的话区间本质不同的查询只有 \(4 \cdot m\) 个,所有数一共也只有 \(b\) 个,可以强行判断每个数是不是询问点的倍数,时间复杂度是 \(O((n + m) \cdot b)\) 的。

这么说理论是 \(b\) 是取 \(\frac{n}{\sqrt{m}}\) 是最优的,但是实际上阈值往小一点取会更好。

Code

/*

*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10, M = 1e7 + 10, B = 48;
int n, m, bel, a[N], mx, blo[N], L[N], R[N];
int dc[N], nm[M], cnt;
ll num[N], p1[N], p2[N], siz[N], ans[N], ret[N];
struct mdzz {
	int l, r, id;
	bool operator < (const mdzz &it) const {
		return blo[l] == blo[it.l] ? r < it.r : l < it.l;
	}
} p[N];
struct query {
	int lt, rt, id, op, tim;
	bool operator < (const query &it) const {
		return tim < it.tim;
	}
} q[N << 2];
const int Mxdt = 100000;
inline char gc() {
	static char buf[Mxdt], *p1 = buf, *p2 = buf;
	return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, Mxdt, stdin), p1 == p2) ? EOF : *p1++;
}
inline int read() {
	char ch = gc();
	int s = 0, w = 1;
	while (!isdigit(ch)) {if (ch == '-') w = -1; ch = gc();}
	while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = gc();}
	return s * w;
}
int main() {
	n = read(); m = read(); bel = sqrt(n);
	for (int i = 1; i <= n; ++i) {
		mx = max(mx, a[i] = read());
		blo[i] = (i - 1) / bel + 1;
	}
	for (int i = 1; i <= m; ++i) p[i] = (mdzz) {read(), read(), i};
	sort(p + 1, p + 1 + m);
	for (int i = 1; i <= mx; ++i) {
		for (int j = i; j <= mx; j += i) ++dc[j];
	}
	for (int i = 1; i <= mx; ++i) {
		L[i] = R[i - 1] + 1;
		R[i] = R[i - 1] + dc[i];
		dc[i] = R[i - 1];
	}
	for (int i = 1; i <= mx; ++i) {
		for (int j = i; j <= mx; j += i) nm[++dc[j]] = i;
	}
	for (int i = 1; i <= n; ++i) {
		p1[i] += p1[i - 1];
		for (int j = L[a[i]]; j <= R[a[i]]; ++j) ++num[nm[j]];
		p1[i] += num[a[i]];
	}
	memset(num, 0, sizeof(num));
	for (int i = 1; i <= n; ++i) {
		p2[i] += p2[i - 1];
		for (int j = L[a[i]]; j <= R[a[i]]; ++j) p2[i] += num[nm[j]];
		++num[a[i]];
	}
	memset(num, 0, sizeof(num));
	for (int i = 1, l = 1, r = 0, ql, qr; i <= m; ++i) {
		ql = p[i].l; qr = p[i].r;
		if (l > ql) {
			q[++cnt] = (query) {ql, l - 1, i, 1, r};
			ans[i] += p1[ql - 1] - p1[l - 1];
			ans[i] += p2[ql - 1] - p2[l - 1];
			l = ql;
		}
		if (l < ql) {
			q[++cnt] = (query) {l, ql - 1, i, -1, r};
			ans[i] += p1[ql - 1] - p1[l - 1];
			ans[i] += p2[ql - 1] - p2[l - 1];
			l = ql;
		}
		if (r < qr) {
			q[++cnt] = (query) {r + 1, qr, i, -1, l - 1};
			ans[i] += p1[qr] - p1[r];
			ans[i] += p2[qr] - p2[r];
			r = qr;
		}
		if (r > qr) {
			q[++cnt] = (query) {qr + 1, r, i, 1, l - 1};
			ans[i] += p1[qr] - p1[r];
			ans[i] += p2[qr] - p2[r];
			r = qr;
		}
	}
	sort(q + 1, q + 1 + cnt);
	int it = 0;
	for (int i = 1, ql, qr; i <= cnt; ++i) {
		while (it < q[i].tim) {
			++it;
			for (int j = L[a[it]]; j <= R[a[it]]; ++j) ++num[nm[j]];
			if (a[it] > B) for (int j = a[it]; j <= mx; j += a[it]) ++num[j];
		}
		ql = q[i].lt; qr = q[i].rt;
		for (int j = ql; j <= qr; ++j) ans[q[i].id] += q[i].op * num[a[j]];
	}
	for (int I = 1; I <= B; ++I) {
		ll res = 0;
		it = 0;
		for (int i = 1; i <= n; ++i) siz[i] = siz[i - 1] + !(a[i] % I);
		for (int i = 1, ql, qr; i <= cnt; ++i) {
			while (it < q[i].tim) {
				++it;
				if (a[it] == I) ++res;
			}
			ql = q[i].lt; qr = q[i].rt;
			ans[q[i].id] += q[i].op * (siz[qr] - siz[ql - 1]) * res;
		}
	}
	for (int i = 1; i <= m; ++i) ret[p[i].id] = (ans[i] += ans[i - 1]);
	for (int i = 1; i <= m; ++i) printf("%lld\n", ret[i]);
	return 0;
}
posted @ 2022-03-09 20:46  Illusory_dimes  阅读(21)  评论(0编辑  收藏  举报