题目传送门: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_{egin{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 !leftlfloor frac{x}{j} ight floor)。
对 (displaystyle leftlfloor frac{x}{j} ight floor) 进行整除分块,只要维护 (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:
", ++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
", Ans);
}
}
}
return 0;
}