bzoj3739: DZY loves math VIII
Description
在XYZ的dzy loves math6问世后,dzy一直觉得这道题答案太大,一点都不优美,于是他随手在外面套上一个μ。同时,他又觉得输入两个数实在太麻烦,于是题目变成了,你能解决这个问题吗?
Input
第一行一个整数T表示询问组数,接下来T行每行一个整数n。
Output
对于每一个询问输出一行表示答案
Sample Input
1
2
Sample Output
0
HINT
T<=10^3 n<=10^7
分析
首先要注意到当且仅当
gcd(i,j)==1
g
c
d
(
i
,
j
)
==
1
才有贡献。
于是变成
ans=∑ni∑ijμ(ij)[gcd(i,j)==1]
a
n
s
=
∑
i
n
∑
j
i
μ
(
i
j
)
[
g
c
d
(
i
,
j
)
==
1
]
=∑ni∑ijμ(i)μ(j)∑k|i,k|jμ(k)
=
∑
i
n
∑
j
i
μ
(
i
)
μ
(
j
)
∑
k
|
i
,
k
|
j
μ
(
k
)
注意到这里不好直接枚举
k
k
,因为如果枚举
=∑nkμ(k)∑nki∑ijμ(i∗k)μ(j∗k)
=
∑
k
n
μ
(
k
)
∑
i
n
k
∑
j
i
μ
(
i
∗
k
)
μ
(
j
∗
k
)
=∑nkμ(k)(∑nkiμ(i∗k)∑nkjμ(j∗k)+∑nkxμ2(x∗k))/2
=
∑
k
n
μ
(
k
)
(
∑
i
n
k
μ
(
i
∗
k
)
∑
j
n
k
μ
(
j
∗
k
)
+
∑
x
n
k
μ
2
(
x
∗
k
)
)
/
2
那个
k
k
很不好处理,虽然可以得到一个的算法(暴力处理
∑μ(i∗k)
∑
μ
(
i
∗
k
)
,
∑μ2(i∗k)
∑
μ
2
(
i
∗
k
)
),但是复杂度不可以接受。
这个时候我们注意到
T
T
比较大,所以需要设计 一个类似递推的方法。
故先枚举,再枚举
k
k
这样的好处是,后面的
∑k|iμ(k)∑ikjμ(j∗k)
∑
k
|
i
μ
(
k
)
∑
j
i
k
μ
(
j
∗
k
)
只和
i
i
有关,每次只要在之前答案的基础上计算
还有一个优秀的性质是,当且仅当
i
i
时square-free(无平方因子),才会对答案有贡献,否则有
而
107<2∗3∗5∗7∗11∗13∗15∗17∗19∗23
10
7
<
2
∗
3
∗
5
∗
7
∗
11
∗
13
∗
15
∗
17
∗
19
∗
23
这意味着每个数最多只有9个不同的素因子,所以枚举
i
i
的复杂度变成了
现在的问题变成了如何快速求出
∑k|iμ(k)∑ikjμ(j∗k)
∑
k
|
i
μ
(
k
)
∑
j
i
k
μ
(
j
∗
k
)
枚举
k
k
的复杂度是的。
剩下处理
f(i,k)=∑ikjμ(j∗k)(k|i)
f
(
i
,
k
)
=
∑
j
i
k
μ
(
j
∗
k
)
(
k
|
i
)
注意到
(k|i)
(
k
|
i
)
,故对于每个
k
k
只需要考虑与
f(i,k)
f
(
i
,
k
)
的关系。
显然有
f(i+k,k)=∑ik+1jμ(j∗k)=f(i,k)+μ(i+k)
f
(
i
+
k
,
k
)
=
∑
j
i
k
+
1
μ
(
j
∗
k
)
=
f
(
i
,
k
)
+
μ
(
i
+
k
)
于是我们仅仅记录
f(k)
f
(
k
)
的值,随
i
i
的变化边做边更新即可。
复杂度,优秀的算法!
分析
#include<cstdio>
const int N = 1e7 + 10;
int f[N], u[N], pr[N], p[N], s[N], q[N], mn[N], tp;
void Pre(int N) {
u[1] = 1;
for(int i = 2;i <= N; ++i) {
if(!mn[i]) pr[++tp] = i, mn[i] = i, u[i] = -1;
for(int j = 1;j <= tp && i * pr[j] <= N; ++j) {
mn[i * pr[j]] = pr[j];
if(i % pr[j]) u[i * pr[j]] = -u[i];
else break;
}
}
}
int Dfs(int k, int x, int v) {
if(k > tp) return f[x] += v, u[x] * f[x];
return Dfs(k + 1, x * p[k], v) + Dfs(k + 1, x, v);
}
int main() {
int T; scanf("%d", &T); int n = 0;
for(int i = 1;i <= T; ++i)
scanf("%d", &q[i]), n < q[i] ? n = q[i] : 0;
Pre(n);
for(int i = 1;i <= n; ++i) {
s[i] = s[i - 1];
if(u[i]) {
tp = 0; for(int x = i; x != 1; x /= mn[x]) p[++tp] = mn[x];
s[i] += u[i] * Dfs(1, 1, u[i]);
}
}
for(int i = 1;i <= T; ++i) printf("%d\n", s[q[i]]);
return 0;
}