洛谷P1447 [NOI2010]能量采集
标签
- 莫比乌斯反演
- 整除分块
前言
简明题意
- 给一个 n ∗ m n*m n∗m的矩阵,如果从 ( 0 , 0 ) 到 ( x , y ) (0,0)到(x,y) (0,0)到(x,y)的连线上点的数量是 c n t cnt cnt(不包含 ( 0 , 0 ) , ( x , y ) (0,0),(x,y) (0,0),(x,y)),那么点 ( x , y ) (x,y) (x,y)的花费就是 2 ∗ c n t + 1 2*cnt +1 2∗cnt+1.问所有点的花费之和。
思路
-
(
0
,
0
)
到
(
x
,
y
)
(0,0)到(x,y)
(0,0)到(x,y)连线上点的数量是
g
c
d
(
x
,
y
)
gcd(x,y)
gcd(x,y)(包含
(
x
,
y
)
(x,y)
(x,y)),那么按照题意得
c
n
t
cnt
cnt就应该是
g
c
d
(
x
,
y
)
−
1
gcd(x,y)-1
gcd(x,y)−1,那么所有点得贡献和就是:
∑ i = 1 n ∑ j = 1 m ( 2 ∗ ( g c d ( i , j ) − 1 ) + 1 ) \sum_{i=1}^n\sum_{j=1}^m\left(2*\left(gcd(i,j)-1\right)+1\right) i=1∑nj=1∑m(2∗(gcd(i,j)−1)+1) - 提出常数,原式就变成:
2 ∗ ∑ i = 1 n ∑ j = 1 m g c d ( i , j ) − n ∗ m 2*\sum_{i=1}^{n}\sum_{j=1}^mgcd(i,j)-n*m 2∗i=1∑nj=1∑mgcd(i,j)−n∗m - 所以,现在的难点就在于如何求
∑
i
=
1
n
∑
j
=
1
m
g
c
d
(
i
,
j
)
\sum\limits_{i=1}^{n}\sum\limits_{j=1}^mgcd(i,j)
i=1∑nj=1∑mgcd(i,j)。但这个实际很简单。我们改为枚举
g
c
d
(
i
,
j
)
gcd(i,j)
gcd(i,j),就变成:
∑ x = 1 n ( x ∗ ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = = x ] ) \sum_{x=1}^n\left(x*\sum\limits_{i=1}^{n}\sum\limits_{j=1}^m[gcd(i,j)==x]\right) x=1∑n(x∗i=1∑nj=1∑m[gcd(i,j)==x]) - 而
∑
i
=
1
n
∑
j
=
1
m
[
g
c
d
(
i
,
j
)
=
=
x
]
\sum\limits_{i=1}^{n}\sum\limits_{j=1}^m[gcd(i,j)==x]
i=1∑nj=1∑m[gcd(i,j)==x]的求法在我的另一篇博客中详细说明了,请戳这里,我们现在直接把结论摆出来:
∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = = x ] = ∑ d = 1 [ n x ] ( μ ( d ) ∗ [ n d x ] ∗ [ m d x ] ) \sum\limits_{i=1}^{n}\sum\limits_{j=1}^m[gcd(i,j)==x]=\sum_{d=1}^{[\frac nx]}\left( \mu(d)*[\frac {n}{dx}]*[\frac {m}{dx}]\right) i=1∑nj=1∑m[gcd(i,j)==x]=d=1∑[xn](μ(d)∗[dxn]∗[dxm]) - 再把这个带入原式,就得到了:
∑ i = 1 n ∑ j = 1 m g c d ( i , j ) = ∑ x = 1 n ( x ∗ ∑ d = 1 [ n x ] ( μ ( d ) ∗ [ n d x ] ∗ [ m d x ] ) ) \sum_{i=1}^{n}\sum_{j=1}^mgcd(i,j)=\sum_{x=1}^{n}\left(x*\sum_{d=1}^{[\frac nx]}\left( \mu(d)*[\frac {n}{dx}]*[\frac {m}{dx}]\right)\right) i=1∑nj=1∑mgcd(i,j)=x=1∑n⎝⎛x∗d=1∑[xn](μ(d)∗[dxn]∗[dxm])⎠⎞ - 预处理用线筛 O ( n ) O(n) O(n), x x x的枚举是 O ( n ) O(n) O(n),后面那一坨可以整除分块,复杂度 O ( n x ) O(\sqrt{\frac nx}) O(xn)。上面式子的复杂度是 O ( n + n ∗ n x ) O(n+n*\sqrt{\frac nx}) O(n+n∗xn),就是 O ( n ∗ n x ) O(n*\sqrt{\frac nx}) O(n∗xn)
注意事项
- 无
总结
- ( 0 , 0 ) (0,0) (0,0)到 ( x , y ) (x,y) (x,y)连线上点的数量是 g c d ( x , y ) gcd(x,y) gcd(x,y),包含 ( x , y ) 但 不 包 含 ( 0 , 0 ) (x,y)但不包含(0,0) (x,y)但不包含(0,0)
AC代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
int n, m;
bool no_prime[maxn];
int prime[maxn], mu[maxn], pre_mu[maxn];
int shai(int n)
{
int cnt = 0;
mu[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!no_prime[i])
prime[++cnt] = i, mu[i] = -1;
for (int j = 1; j <= cnt && prime[j] * i <= n; j++)
{
no_prime[prime[j] * i] = 1;
mu[prime[j] * i] = i % prime[j] == 0 ? 0 : -mu[i];
if (i % prime[j] == 0) break;
}
}
for (int i = 1; i <= n; i++)
pre_mu[i] = pre_mu[i - 1] + mu[i];
return cnt;
}
int cal2(int n, int m, int x)
{
n = n / x, m = m / x;
int l = 1, r, ans = 0;
while (l <= n)
{
r = min(n / (n / l), m / (m / l));
ans += (pre_mu[r] - pre_mu[l - 1]) * (n / l) * (m / l);
l = r + 1;
}
return ans;
}
void solve()
{
scanf("%d%d", &n, &m);
if (m < n) swap(n, m);
shai(m);
long long ans = 0;
for (int x = 1; x <= n; x++)
ans += 1ll * x * cal2(n, m, x);
printf("%lld", 2 * ans - n * m);
}
int main()
{
solve();
return 0;
}