原题链接:
SPOJ
bzoj
洛谷上的重题
有 3 e 5 3e5 3e5组询问,每次给定一个 n ( n < = 1000000 ) n(n<=1000000) n(n<=1000000),求:
l c m ( 1 , n ) + l c m ( 2 , n ) . . . + l c m ( n , n ) lcm(1,n)+lcm(2,n)...+lcm(n,n) lcm(1,n)+lcm(2,n)...+lcm(n,n)
T //询问个数
n
n
...
n//每个n
ans
ans
...
ans//对于每个询问,输出答案
暴力推式子。来♂
还是设 g = g c d ( i , n ) g=gcd(i,n) g=gcd(i,n),注意不是常数,是宏定义,相当于#define
原 式 = ∑ i = 1 n l c m ( i , n ) = ∑ i = 1 n n i g = n ∑ i = 1 n i g ( 常 规 变 形 , 拆 l c m 为 g c d ) = n ∑ d ∣ n ∑ i = 1 n [ g = = d ] i d ( 枚 举 g c d 的 值 , 去 判 断 在 哪 些 i 中 被 算 到 ) = n ∑ d ∣ n ∑ i = 1 n [ g c d ( i d , n d ) = = 1 ] i d ( 括 号 里 面 除 以 d ) = n ∑ d ∣ n ∑ i = 1 n d [ g c d ( i , n d ) = = 1 ] i ( 把 i 的 定 义 换 成 : 枚 举 i d ) = n ∑ d ∣ n ∑ i = 1 d [ g c d ( i , d ) = = 1 ] i ( 因 为 我 们 枚 举 的 d 是 n 的 因 数 , 所 以 d 和 n / d 应 该 是 对 称 的 , 把 n / d 换 成 d , 只 是 枚 举 顺 序 反 了 一 下 , 不 改 变 答 案 ) 原式\\ =\sum\limits_{i=1}^{n}lcm(i,n)\\ =\sum\limits_{i=1}^{n}\frac{ni}{g}\\ =n\sum\limits_{i=1}^{n}\frac{i}{g}\\ (常规变形,拆lcm为gcd)\\ =n\sum\limits_{d|n}\sum\limits_{i=1}^{n}[g==d]\frac{i}{d}\\ (枚举gcd的值,去判断在哪些i中被算到)\\ =n\sum\limits_{d|n}\sum\limits_{i=1}^{n}[gcd(\frac{i}{d},\frac{n}{d})==1]\frac{i}{d}\\ (括号里面除以d)\\ =n\sum\limits_{d|n}\sum\limits_{i=1}^{\frac{n}{d}}[gcd(i,\frac{n}{d})==1]i\\ (把i的定义换成:枚举\frac{i}{d})\\ =n\sum\limits_{d|n}\sum\limits_{i=1}^{d}[gcd(i,d)==1]i\\ (因为我们枚举的d是n的因数,所以d和n/d应该是对称的,把n/d换成d,只是枚举顺序反了一下,不改变答案) 原式=i=1∑nlcm(i,n)=i=1∑ngni=ni=1∑ngi(常规变形,拆lcm为gcd)=nd∣n∑i=1∑n[g==d]di(枚举gcd的值,去判断在哪些i中被算到)=nd∣n∑i=1∑n[gcd(di,dn)==1]di(括号里面除以d)=nd∣n∑i=1∑dn[gcd(i,dn)==1]i(把i的定义换成:枚举di)=nd∣n∑i=1∑d[gcd(i,d)==1]i(因为我们枚举的d是n的因数,所以d和n/d应该是对称的,把n/d换成d,只是枚举顺序反了一下,不改变答案)
现在我们要求这样一个东西:
对于任意一个 < = n <=n <=n的数 x x x,求 [ 1 , x ] [1,x] [1,x]中所有和 x x x互质的数的和。
我们能求什么呢?
[ 1 , x ] [1,x] [1,x]中所有和 x x x互质的数的个数。很明显这个就是 ϕ ( x ) \phi(x) ϕ(x)。
珂是。。。如果变成求和。。。怎么办呢?好像不太好求。
我们回想一下高斯是怎么算 [ 1 , 100 ] [1,100] [1,100]的和的。显然他知道 [ 1 , 100 ] [1,100] [1,100]里面的个数( 100 100 100个)。然后他很聪明,把序列反过来写一遍,发现和相等,这样和就是 100 ∗ 101 / 2 100*101/2 100∗101/2。
珂是我为什么要讲高斯的故事呢?因为这个和高斯很像。我们不妨设 x = 20 x=20 x=20,观察一下和 20 20 20互质的数,把逆序,逆序+正序,都写在下面:
1 ,3 , 7, 9,11,13,17,19
19,17,13,11, 9, 7, 3, 1
20,20,20,20,20,20,20,20
发现了什么?当然,如果你把 x x x代入别的验证,也是这样的。这就是我们发现的规律。
此时,两个序列的和就是 n ∗ ϕ ( n ) n*\phi(n) n∗ϕ(n)。显然,两个序列的和都是相等的。那么其中一个序列的和就是: n ∗ ϕ ( n ) 2 \frac{n*\phi(n)}{2} 2n∗ϕ(n)。也就是我们要求的那个东西。
(不过要特判一个 n = 1 n=1 n=1,此时的和为 1 1 1,而不是 n ∗ p h i ( n ) / 2 = 1 2 n*phi(n)/2=\frac{1}{2} n∗phi(n)/2=21)
还记得我们刚刚的答案式子是什么吗?是 n ∑ d ∣ n ∑ i = 1 d [ g c d ( i , d ) = = 1 ] i n\sum\limits_{d|n}\sum\limits_{i=1}^{d}[gcd(i,d)==1]i nd∣n∑i=1∑d[gcd(i,d)==1]i。然后我们现在珂以把后面优化掉了,变成:
n ∑ d ∣ n d ∗ ϕ ( d ) 2 n\sum\limits_{d|n}\frac{d*\phi(d)}{2} nd∣n∑2d∗ϕ(d)
至此,我们珂以每个询问 O ( n ) O(\sqrt{n}) O(n)加特技,卡常数,水过这个题。
当然,我们也有更快的方法。考虑预处理。设一个数组,一开始都是 0 0 0。枚举每个 d d d和 d d d的倍数,在 d d d的倍数上加上 d ∗ ϕ ( d ) 2 \frac{d*\phi(d)}{2} 2d∗ϕ(d)的值(特判 d = 1 d=1 d=1)。虽然这样预处理是 O ( n l o g n ) O(nlogn) O(nlogn)的(调和级数),但是每次询问就珂以 O ( 1 ) O(1) O(1)出来了。相比刚刚那个算法,虽然多耗了一点思考,但是省了很多卡常数的力气,珂以说是优秀的三好算法了。
(三好算法)代码:
#include
#define N 1001000
using namespace std;
bool notp[N];int primes[N],phi[N];
long long f[N];
void Init()
{
int& cnt=primes[0];
int n=1000000;
notp[1]=1;
phi[1]=1;
for(int i=2;i<=n;i++)
{
if (!notp[i])
{
primes[++cnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=cnt and i*primes[j]<=n;j++)
{
int u=primes[j];
notp[i*u]=1;
if (i%u!=0)
{
phi[i*u]=phi[i]*phi[u];
}
else
{
phi[i*u]=phi[i]*u;
break;
}
}
}//预处理phi
for(int i=1;i<=n;i++)
{
for(int j=1;i*j<=n;j++)
{
f[i*j]+=(i==1?1:1ll*phi[i]*i/2);
}
}//预处理,O(nlogn)
}
int n;
int main()
{
Init();
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
printf("%lld\n",f[n]*1ll*n);
}
return 0;
}
//代码是很久之前写的,好久不整理博客了。所以码风有点。。。。。。。
//虽然常数小,但也是卡了一些常数,比如我没有全部用long long
回到总题解界面