BZOJ 2154题目链接
BZOJ 2693题目链接(权限题)
再送上一道类似推导思路的题:
NJU 1017题目链接
参考文档
(用了一整天 终于刷完文档例题了 qwq)
题意:
给定
n
,
m
(
<
=
1
e
7
)
n,m(<=1e7)
n,m(<=1e7) 求
∑
i
=
1
n
∑
j
=
1
m
l
c
m
(
i
,
j
)
\sum_{i =1}^n \sum_{j=1}^m lcm(i,j)
i=1∑nj=1∑mlcm(i,j)
第一题是单组数据,第二题是多组数据。
思路:
开始写了一个
O
(
n
n
)
O(n\sqrt n)
O(nn)的算法 果断T。
下面试着推导一个
O
(
n
)
O(n)
O(n)甚至
O
(
n
)
O(\sqrt n)
O(n)的计算表达式。
设答案为
A
n
s
Ans
Ans,由
l
c
m
lcm
lcm定义,可得:
A
n
s
=
∑
i
=
1
n
∑
j
=
1
m
i
j
g
c
d
(
i
,
j
)
Ans = \sum_{i =1}^n \sum_{j=1}^m \frac{ij}{gcd(i,j)}
Ans=i=1∑nj=1∑mgcd(i,j)ij
但这么直接计算复杂度是
O
(
n
m
)
O(nm)
O(nm)严重爆炸
故考虑枚举
g
c
d
(
i
,
j
)
gcd(i,j)
gcd(i,j)(设为
g
g
g)的值,想办法应用莫比乌斯反演进行化简。
A
n
s
=
∑
g
=
1
m
i
n
(
n
,
m
)
(
∑
i
=
1
n
∑
j
=
1
m
i
j
g
(
g
c
d
(
i
,
j
)
=
=
g
)
)
Ans = \sum_{g=1}^{min(n,m)} ( \sum_{i=1}^n\sum_{j=1}^m \frac{ij}{g}(gcd(i,j) == g))
Ans=g=1∑min(n,m)(i=1∑nj=1∑mgij(gcd(i,j)==g))
进一步化简:(转换枚举变量,使
g
c
d
=
1
gcd=1
gcd=1)
A
n
s
=
∑
g
=
1
m
i
n
(
n
,
m
)
(
∑
i
=
1
n
/
g
∑
j
=
1
m
/
g
g
2
∗
i
j
g
(
g
c
d
(
i
,
j
)
=
=
1
)
)
Ans = \sum_{g=1}^{min(n,m)} ( \sum_{i=1}^{n/g}\sum_{j=1}^{m/g} \frac{g^2*ij}{g}(gcd(i,j) == 1))
Ans=g=1∑min(n,m)(i=1∑n/gj=1∑m/ggg2∗ij(gcd(i,j)==1))
得:
A
n
s
=
∑
g
=
1
m
i
n
(
n
,
m
)
g
(
∑
i
=
1
n
/
g
∑
j
=
1
m
/
g
i
j
(
g
c
d
(
i
,
j
)
=
=
1
)
)
Ans = \sum_{g=1}^{min(n,m)} g( \sum_{i=1}^{n/g}\sum_{j=1}^{m/g} ij(gcd(i,j) == 1))
Ans=g=1∑min(n,m)g(i=1∑n/gj=1∑m/gij(gcd(i,j)==1))
令 f ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j ( g c d ( i , j ) = = 1 ) f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == 1) f(x,y)=i=1∑xj=1∑yij(gcd(i,j)==1)
则:
A
n
s
=
∑
g
=
1
m
i
n
(
n
,
m
)
g
f
(
n
g
,
m
g
)
Ans = \sum_{g=1}^{min(n,m)} gf(\frac{n}{g},\frac{m}{g})
Ans=g=1∑min(n,m)gf(gn,gm)
故问题关键便在于如何来求解
f
(
x
,
y
)
f(x,y)
f(x,y)
此处介绍两种推导方式,一种是利用莫比乌斯函数的性质,另一种则是直接使用变换式。
利用 ∑ d ∣ n μ ( d ) = ( n = = 1 ) \sum_{d|n}\mu(d) = (n==1) ∑d∣nμ(d)=(n==1)
因
f
(
x
,
y
)
=
∑
i
=
1
x
∑
j
=
1
y
i
j
(
g
c
d
(
i
,
j
)
=
=
1
)
f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == 1)
f(x,y)=i=1∑xj=1∑yij(gcd(i,j)==1)
将
g
c
d
(
i
,
j
)
gcd(i,j)
gcd(i,j)代入性质表达式中的
n
n
n,得:
f
(
x
,
y
)
=
∑
i
=
1
x
∑
j
=
1
y
i
j
∑
d
∣
g
c
d
(
i
,
j
)
μ
(
d
)
f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij \sum_{d|gcd(i,j)}\mu(d)
f(x,y)=i=1∑xj=1∑yijd∣gcd(i,j)∑μ(d)
因
d
∣
g
c
d
(
i
,
j
)
d|gcd(i,j)
d∣gcd(i,j),故
i
,
j
i,j
i,j一定是
d
d
d的倍数
设
i
=
k
1
d
j
=
k
2
d
i= k_1d \space j = k_2d
i=k1d j=k2d
转换枚举变量为
d
d
d,则:
f
(
x
,
y
)
=
∑
d
=
1
m
i
n
(
x
,
y
)
∑
k
1
=
1
x
d
∑
k
2
=
1
y
d
k
1
k
2
d
2
μ
(
d
)
f(x,y) = \sum_{d=1}^{min(x,y)} \sum_{k_1=1}^{\frac{x}{d}}\sum_{k_2=1}^{\frac{y}{d}} k_1k_2d^2\mu(d)
f(x,y)=d=1∑min(x,y)k1=1∑dxk2=1∑dyk1k2d2μ(d)
即:
f
(
x
,
y
)
=
∑
d
=
1
m
i
n
(
x
,
y
)
d
2
μ
(
d
)
∑
k
1
=
1
x
d
∑
k
2
=
1
y
d
k
1
k
2
f(x,y) = \sum_{d=1}^{min(x,y)} d^2\mu(d) \sum_{k_1=1}^{\frac{x}{d}}\sum_{k_2=1}^{\frac{y}{d}} k_1k_2
f(x,y)=d=1∑min(x,y)d2μ(d)k1=1∑dxk2=1∑dyk1k2
设 s u m ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j = ∑ i = 1 x i ∑ j = 1 y j = x ( x + 1 ) 2 y ( y + 1 ) 2 sum(x,y) = \sum_{i=1}^x\sum_{j=1}^y ij =\sum_{i=1}^x i \sum_{j=1}^y j = \frac{x(x+1)}{2}\frac{y(y+1)}{2} sum(x,y)=i=1∑xj=1∑yij=i=1∑xij=1∑yj=2x(x+1)2y(y+1)
故
f
(
x
,
y
)
=
∑
d
=
1
m
i
n
(
x
,
y
)
d
2
μ
(
d
)
s
u
m
(
x
d
,
y
d
)
f(x,y) = \sum_{d=1}^{min(x,y)} d^2\mu(d) sum(\frac{x}{d},\frac{y}{d})
f(x,y)=d=1∑min(x,y)d2μ(d)sum(dx,dy)
故我们可以 O ( n ) O(n) O(n)预处理 d 2 μ ( d ) d^2\mu(d) d2μ(d)的前缀和,然后两次 O ( n ) O(\sqrt n) O(n)的分块除法,总复杂度 O ( n ) O(n) O(n)下解决问题。
下面再说说如何直接利用反演表达式推出此公式。
利用 F ( n ) = ∑ n ∣ d f ( d ) = > f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) F(n)=\sum_{n|d}f(d) => f(n) = \sum_{n|d} \mu(\frac{d}{n}) F(d) F(n)=∑n∣df(d)=>f(n)=∑n∣dμ(nd)F(d)
因
f
(
x
,
y
)
=
∑
i
=
1
x
∑
j
=
1
y
i
j
(
g
c
d
(
i
,
j
)
=
=
1
)
f(x,y) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == 1)
f(x,y)=i=1∑xj=1∑yij(gcd(i,j)==1)
不妨设
g
(
n
)
=
∑
i
=
1
x
∑
j
=
1
y
i
j
(
g
c
d
(
i
,
j
)
=
=
n
)
g(n) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(gcd(i,j) == n)
g(n)=i=1∑xj=1∑yij(gcd(i,j)==n)
故
g
(
1
)
=
f
(
x
,
y
)
g(1) = f(x,y)
g(1)=f(x,y)
又设
G
(
n
)
=
∑
i
=
1
x
∑
j
=
1
y
i
j
(
n
∣
g
c
d
(
i
,
j
)
)
G(n) = \sum_{i=1}^{x}\sum_{j=1}^{y} ij(n|gcd(i,j))
G(n)=i=1∑xj=1∑yij(n∣gcd(i,j))
故可得:
G
(
n
)
=
∑
n
∣
d
g
(
d
)
=
>
g
(
n
)
=
∑
n
∣
d
μ
(
d
n
)
G
(
d
)
G(n)=\sum_{n|d}g(d) => g(n) = \sum_{n|d} \mu(\frac{d}{n}) G(d)
G(n)=n∣d∑g(d)=>g(n)=n∣d∑μ(nd)G(d)
所以:
g
(
1
)
=
∑
d
=
1
m
i
n
(
x
,
y
)
μ
(
d
)
G
(
d
)
g(1) = \sum_{d=1}^{min(x,y)} \mu(d) G(d)
g(1)=d=1∑min(x,y)μ(d)G(d)
故此时的关键在于
G
G
G 函数的求解
同上第一个推导,设:
s
u
m
(
x
,
y
)
=
∑
i
=
1
x
∑
j
=
1
y
i
j
=
∑
i
=
1
x
i
∑
j
=
1
y
j
=
x
(
x
+
1
)
2
y
(
y
+
1
)
2
sum(x,y) = \sum_{i=1}^x\sum_{j=1}^y ij =\sum_{i=1}^x i \sum_{j=1}^y j = \frac{x(x+1)}{2}\frac{y(y+1)}{2}
sum(x,y)=i=1∑xj=1∑yij=i=1∑xij=1∑yj=2x(x+1)2y(y+1)
由
G
G
G定义,易得:
G
(
d
)
=
∑
i
=
1
x
/
d
∑
j
=
1
y
/
d
i
j
∗
d
2
=
d
2
s
u
m
(
x
d
,
y
d
)
G(d) = \sum_{i=1}^{x/d}\sum_{j=1}^{y/d}ij*d^2 = d^2sum(\frac{x}{d},\frac{y}{d})
G(d)=i=1∑x/dj=1∑y/dij∗d2=d2sum(dx,dy)
综上知:
f
(
x
,
y
)
=
g
(
1
)
=
∑
d
=
1
m
i
n
(
x
,
y
)
d
2
μ
(
d
)
s
u
m
(
x
d
,
y
d
)
f(x,y) = g(1) = \sum_{d=1}^{min(x,y)} d^2\mu(d) sum(\frac{x}{d},\frac{y}{d})
f(x,y)=g(1)=d=1∑min(x,y)d2μ(d)sum(dx,dy)
两种推导方式推出的结果已经足够解决第一题:
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 20101009;
const int A = 1e7 + 10;
bool vis[A];
int pri[A],mu[A],tot;
int n,m;
ll sum[A];
void init(){
tot = 0;mu[1] = 1;
for(ll i=2 ;i<=n ;i++){
if(vis[i] == 0){pri[++tot] = i;mu[i] = -1;}
for(int j=1 ;j<=tot && i*pri[j] <= n ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){mu[i*pri[j]] = 0;break;}
mu[i*pri[j]] = -mu[i];
}
}
for(ll i=1 ;i<=n ;i++) sum[i] = (sum[i-1] + i*i%mod*mu[i]%mod)%mod;
}
ll S(ll x,ll y){
x %= mod;y %= mod;
ll res1 = x*(x+1)/2%mod;
ll res2 = y*(y+1)/2%mod;
return res1*res2%mod;
}
ll solve(ll x,ll y){
ll last,res = 0;
for(ll i=1 ;i<=min(x,y) ;i=last+1){
last = min(x/(x/i),y/(y/i));
res = (res + (sum[last] - sum[i-1])*S(x/i,y/i)%mod)%mod;
}
if(res<0) res+=mod;
return res;
}
int main(){
scanf("%d%d",&n,&m);if(n>m) swap(n,m);
init();
ll last,ans = 0;
for(ll i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans = (ans + (i+last)*(last-i+1)/2%mod*solve(n/i,m/i)%mod)%mod;
}
if(ans < 0) ans += mod;
printf("%lld\n",ans);
}
但对于有多组输入的第二题,
O
(
n
)
O(n)
O(n)的复杂度还是不能在时限内得出答案。
让我们再仔细观察一下最终的结果表达式:
A n s = ∑ g = 1 m i n ( n , m ) g ∑ d = 1 m i n ( n g , m g ) d 2 μ ( d ) s u m ( n d g , m d g ) Ans = \sum_{g=1}^{min(n,m)}g \sum_{d=1}^{min(\frac{n}{g},\frac{m}{g})}d^2\mu(d)sum(\frac{n}{dg},\frac{m}{dg}) Ans=g=1∑min(n,m)gd=1∑min(gn,gm)d2μ(d)sum(dgn,dgm)
令
D
=
d
g
D = dg
D=dg我们再一次转化枚举变量。
A
n
s
=
∑
D
=
1
m
i
n
(
n
,
m
)
s
u
m
(
n
D
,
m
D
)
∑
d
∣
D
d
2
μ
(
d
)
D
d
Ans = \sum_{D=1}^{min(n,m)}sum(\frac{n}{D},\frac{m}{D}) \sum_{d|D}d^2\mu(d)\frac{D}{d}
Ans=D=1∑min(n,m)sum(Dn,Dm)d∣D∑d2μ(d)dD
易发现,后面一个求和的表达式是一个可以线性筛出的积性函数。
故我们可以
O
(
n
)
O(n)
O(n)预处理,然后每组数据
O
(
n
)
O(\sqrt n)
O(n)得出答案。
至此 该题大功告成。
同样该转化式也可以用在优化第一题:
代码:(第一题 BZOJ 2154)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 20101009;
const int A = 1e7 + 10;
bool vis[A];
int pri[A],tot;
ll sum[A],h[A];
int n,m;
void init(){
tot = 0;h[1] = 1;
for(ll i=2 ;i<=n ;i++){
if(vis[i] == 0){pri[++tot] = i;h[i] =(i-1LL*i*i)%mod;}
for(int j=1 ;j<=tot && i*pri[j] <= n ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){h[i*pri[j]] = 1LL*pri[j]*h[i]%mod;break;}
h[i*pri[j]] = h[pri[j]]*h[i]%mod;
}
}
for(int i=1 ;i<=n ;i++) sum[i] = ((h[i] + sum[i-1])%mod + mod)%mod;
}
ll S(ll n,ll m){
n %= mod;m %= mod;
return ((n*(n+1)/2)%mod) * ((m*(m+1)/2)%mod)%mod;
}
int main(){
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
init();
ll ans = 0;
int last;
if(n>m) swap(n,m);
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans = (ans + S(n/i,m/i)*(sum[last] - sum[i-1])%mod)%mod;
}
printf("%lld\n",(ans%mod+mod)%mod);
return 0;
}
代码:(第二题 BZOJ 2693)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 100000009;
const int A = 1e7 + 10;
bool vis[A];
int pri[A],tot;
ll sum[A],h[A];
void init(){
tot = 0;h[1] = 1;
for(ll i=2 ;i<A ;i++){
if(vis[i] == 0){pri[++tot] = i;h[i] =(i-1LL*i*i)%mod;}
for(int j=1 ;j<=tot && i*pri[j] < A ;j++){
vis[i*pri[j]] = 1;
if(i%pri[j] == 0){h[i*pri[j]] = 1LL*pri[j]*h[i]%mod;break;}
h[i*pri[j]] = h[pri[j]]*h[i]%mod;
}
}
for(int i=1 ;i<A ;i++) sum[i] = ((h[i] + sum[i-1])%mod + mod)%mod;
}
ll S(ll n,ll m){
n %= mod;m %= mod;
return ((n*(n+1)/2)%mod) * ((m*(m+1)/2)%mod)%mod;
}
int main(){
init();
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
ll ans = 0;
int last;
if(n>m) swap(n,m);
for(int i=1 ;i<=n ;i=last+1){
last = min(n/(n/i),m/(m/i));
ans = (ans + S(n/i,m/i)*(sum[last] - sum[i-1])%mod)%mod;
}
printf("%lld\n",(ans%mod+mod)%mod);
}
return 0;
}