bzoj 3853: GCD Array

题目传送门:bzoj 3853

题意简述:

有一个数组 \(a\),下标范围为 \(1 \sim l\),初始都是 \(0\)

\(Q\) 次操作,为以下两类之一:

  1. 1 n d v:对于所有满足 \(\gcd(i, n) = d\) 的下标 \(i\),令 \(a_i\) 加上 \(v\)
  2. 2 x:求 \(\displaystyle \sum_{i = 1}^{x} a_i\)

数据范围:\(Q \le 5 \cdot {10}^4\)\(n \le 2 \cdot {10}^5\),运算在 long long 范围内。

题解:

操作 1,如果 \(d\) 不是 \(n\) 的因数,就没用。

否则把 \(n\) 除掉 \(d\),则可以变换为:对于所有满足 \(\gcd(i, n) = 1\)\(i\),令 \(a_{i \cdot d}\) 加上 \(v\)

也就是说,每个 \(a_{i \cdot d}\),要加上 \(\displaystyle v \sum_{\begin{subarray}{c} k | i \\ k | n \end{subarray}} \mu(k)\)

那么我们先把条件 \(k | n\) 提前,先枚举 \(k\),变成:

枚举 \(k | n\),对于所有 \(kd\) 的倍数 \(i\),令 \(a_i\) 加上 \(v \mu(k)\)

我们发现,这是:每次操作令下标为某个数的倍数的位置加上 \(v\)

那么我们不维护原数组了,直接维护一个 \(c\) 数组,如果 \(c_i = x\),表示原数组中下标为 \(i\) 的倍数的位置都加上了 \(x\)

则可以得到:\(\displaystyle a_i = \sum_{j | i} c_j\)

那么询问的时候就是查询 \(\displaystyle \sum_{i = 1}^{x} \sum_{j | i} c_j\)

交换求和顺序:\(\displaystyle \sum_{j = 1}^{x} c_j \!\left\lfloor \frac{x}{j} \right\rfloor\)

\(\displaystyle \left\lfloor \frac{x}{j} \right\rfloor\) 进行整除分块,只要维护 \(c\) 的区间和,用树状数组维护即可。

代码如下,时间复杂度为 \(\mathcal O (Q \sqrt{n} \log l)\)

#include <cstdio>

typedef long long LL;
const int MN = 200005, MP = 17985;
const int ML = 50005;

bool ip[MN];
int p[MP], pc;
int mu[MN];
int h[MN], nxt[1641422], to[1641422], tot;
inline void ins(int x, int y) { nxt[++tot] = h[x], to[tot] = y, h[x] = tot; }
inline void Sieve(int N) {
	mu[1] = 1;
	for (int i = 2; i <= N; ++i) {
		if (!ip[i]) p[++pc] = i, mu[i] = -1;
		for (int j = 1, k; j <= pc; ++j) {
			if ((k = p[j] * i) > N) break;
			ip[k] = 1;
			if (i % p[j]) mu[k] = -mu[i];
			else break;
		}
	}
	for (int i = 1; i <= N; ++i) if (mu[i])
		for (int j = i; j <= N; j += i) ins(j, i);
}

int Len, Q;
LL bit[ML];
inline void Add(int i, int x) { for (; i <= Len; i += i & -i) bit[i] += x; }
inline LL Qur(int i) { LL a = 0; for (; i; i -= i & -i) a += bit[i]; return a; }

int main() {
	Sieve(200000);
	int T = 0;
	while (~scanf("%d%d", &Len, &Q) && Len && Q) {
		for (int i = 1; i <= Len; ++i) bit[i] = 0;
		printf("Case #%d:\n", ++T);
		while (Q--) {
			int opt;
			scanf("%d", &opt);
			if (opt == 1) {
				int n, d, v;
				scanf("%d%d%d", &n, &d, &v);
				if (n % d) continue;
				n /= d;
				for (int id = h[n]; id; id = nxt[id]) {
					int k = to[id];
					if (k * d <= Len) Add(k * d, v * mu[k]);
				}
			} else {
				int x;
				scanf("%d", &x);
				LL Ans = 0;
				for (int i = 1, j, k; i <= x; i = j + 1) {
					k = x / i, j = x / k;
					Ans += (LL)k * (Qur(j) - Qur(i - 1));
				}
				printf("%lld\n", Ans);
			}
		}
	}
	return 0;
}
posted @ 2020-02-07 20:25  粉兔  阅读(303)  评论(0编辑  收藏  举报