bzoj 3529 [Sdoi2014]数表 莫比乌斯反演+树状数组+离线处理

题面

题目传送门

解法

  • 显然,对于每一个格子 ( i , j ) (i,j) (i,j),在不管限制的情况下它对答案的贡献为 σ ( g c d ( i , j ) ) \sigma(gcd(i,j)) σ(gcd(i,j)) σ ( i ) \sigma(i) σ(i)表示 i i i的约数和)。
  • 那么我们不妨考虑每一个 d = g c d ( i , j ) d=gcd(i,j) d=gcd(i,j)对整个答案的贡献,应该是 σ ( d ) ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = = d ] \sigma(d)\sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)==d] σ(d)i=1nj=1m[gcd(i,j)==d]
  • 反演一下就可以得到 ∑ k = 1 n ⌊ n k ⌋ ⌊ m k ⌋ ∑ d ∣ k μ ( k d ) σ ( d ) \sum_{k=1}^n\lfloor\frac{n}{k}\rfloor\lfloor\frac{m}{k}\rfloor\sum_{d|k}\mu(\frac{k}{d})\sigma(d) k=1nknkmdkμ(dk)σ(d)
  • 然后再重新考虑一下询问中 a a a的限制,我们考虑把询问离线下来按照 a a a的大小排序,同时也把所有数按照 σ ( i ) \sigma(i) σ(i)排序。
  • 那么我们考虑在把 i i i一个一个加进来的时候怎么维护答案。令 f ( i ) = ∑ d ∣ i μ ( i d ) σ ( d ) f(i)=\sum_{d|i}\mu(\frac{i}{d})\sigma(d) f(i)=diμ(di)σ(d),那么我们只要维护出 f ( i ) f(i) f(i)的前缀和就可以了。维护的话直接暴力用 O ( log ⁡ 2 n ) O(\log^2n) O(log2n)的复杂度更新,这个用树状数组实现即可。
  • 时间复杂度: O ( q ( n log ⁡ n + log ⁡ 2 n ) ) O(q(\sqrt n\log n+\log^2n)) O(q(n logn+log2n))
  • 因为是对 2 31 2^{31} 231取模,所以直接自然溢出就可以了。

代码

#include <bits/stdc++.h>
#define N 100010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x > y ? y : x;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
	int x, id;
	bool operator < (const Node &a) const {return x < a.x;}
} a[N];
struct Que {
	int n, m, s, id;
	bool operator < (const Que &b) const {
		if (s != b.s) return s < b.s;
		return n < b.n;
	}
} b[N];
struct BIT {
	int f[N];
	int lowbit(int x) {return x & -x;}
	void modify(int x, int v) {
		for (; x <= 1e5; x += lowbit(x)) f[x] += v;
	}
	int query(int x) {
		int ret = 0;
		for (; x; x -= lowbit(x)) ret += f[x];
		return ret;
	}
} T;
bool f[N]; int p[N], s[N], mu[N], ans[N];
void sieve(int n) {
	mu[1] = 1; int len = 0;
	memset(f, true, sizeof(f));
	for (int i = 1; i <= n; i++)
		for (int j = i; j <= n; j += i)
			s[j] += i;
	for (int i = 2; i <= n; i++) {
		if (f[i]) p[++len] = i, mu[i] = -1;
		for (int j = 1; j <= len && i * p[j] <= n; j++) {
			int k = i * p[j]; f[k] = false;
			if (i % p[j] == 0) {mu[k] = 0; break;}
			mu[k] = -mu[i];
		}
	}
	for (int i = 1; i <= n; i++) a[i] = (Node) {s[i], i};
	sort(a + 1, a + n + 1);
}
int solve(int n, int m) {
	int ret = 0, x = 0;
	for (int i = 1; i <= n; i = x + 1) {
		x = min(n / (n / i), m / (m / i));
		ret += (T.query(x) - T.query(i - 1)) * (n / i) * (m / i);
	}
	return ret;
}
int main() {
	sieve(1e5); int q; read(q);
	for (int i = 1; i <= q; i++) {
		int n, m, s; read(n), read(m), read(s);
		if (n > m) swap(n, m);
		b[i] = (Que) {n, m, s, i};
	}
	sort(b + 1, b + q + 1);
	int sum = 0, l = 1;
	for (int i = 1; i <= q; i++) {
		while (l <= 1e5 && a[l].x <= b[i].s) {
			int x = a[l++].id;
			for (int j = x; j <= 1e5; j += x)
				T.modify(j, s[x] * mu[j / x]);
		}
		ans[b[i].id] = solve(b[i].n, b[i].m);
		if (ans[b[i].id] < 0) ans[b[i].id] += 1ll << 31;
	}
	for (int i = 1; i <= q; i++) cout << ans[i] << "\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值