bzoj 3853: GCD Array
题目传送门:bzoj 3853。
题意简述:
有一个数组 \(a\),下标范围为 \(1 \sim l\),初始都是 \(0\)。
有 \(Q\) 次操作,为以下两类之一:
1 n d v
:对于所有满足 \(\gcd(i, n) = d\) 的下标 \(i\),令 \(a_i\) 加上 \(v\)。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;
}