题目链接:bzoj2226 .
—————————————-
概述
题目大意如下:
给定正整数
n
,求
题目有多组数据。 (T≤300000, n≤1000000)
lcm(x,y) 即 x,y 的最小公倍数。
—————————————-
分析
对于 lcm(i,n) ,我们很显然可以把它转化成如下形式:
对于某个
i
,我们令
所以,我们不妨枚举
d
,再枚举所有与
我们得到下式:
化到这里,问题就变成了:对于
n
所有的约数
那么关键来了,所有与 nd 互质的数的和怎么求呢?
——————–
结论:
∑i=1ni×[gcd(i,n)=1] = n×φ(n)2.
特别地, n=1 时值为1.
证明:
引理: 对于一个与
n
互质的数
引理证明:设
我们要求的是与 n 互质的所有数的和,即:
根据引理,我们又有:
结论证毕。
——————–
有了这个结论就好办了,我们原来要求的是:
现在我们可以将它转化为:
其中,加
1
是因为当
至此,有2种做法,一个是80分,一个是100分。
80分:
待求:
我们可以线性筛出所有的
φ(d)
,对于每个询问
O(n√)
枚举
n
所有约数
代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
#define For(i,j,k) for(register int i=j; i<=(int)k; ++i)
#define Forr(i,j,k) for(register int i=j; i>=(int)k; --i)
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1000000+5;
int T, n, tot;
int pri[maxn/5], phi[maxn];
bool vis[maxn];
inline void read(int &x){
x = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = (x<<3)+(x<<1)+(c^48), c = getchar();
}
inline void init(){
phi[1] = 1;
For(i, 2, maxn-1){
if(!vis[i]){
pri[++tot] = i;
phi[i] = i-1;
}
for(int j=1,x; j<=tot&&(x=i*pri[j])<maxn; ++j){
vis[x] = true;
if(i%pri[j] == 0){
phi[x] = phi[i]*pri[j];
break;
}
else phi[x] = phi[i]*(pri[j]-1);
}
}
}
inline ll calc(int n){
ll sum, back;
sum = 1, back = 1ll * n;
for(int i = 1; i*i<=n; ++i)
if(n%i == 0){
sum += 1ll * phi[i] * i >> 1;
if(i*i == n) break;
sum += 1ll * phi[n/i] * (n/i) >> 1;
}
back *= sum;
return back;
}
int main(){
init();
read(T);
while(T --){
read(n);
printf("%lld\n", calc(n));
}
return 0;
}
100分
待求:
我们令 h(n)=∑d|nd×φ(d) ,那么答案即是:
我们观察一下
h(n)=∑d|nd×φ(d)
,由于
d
与
既然 h(n) 是积性函数,那么就可以通过线性筛筛出来,我们思考一下怎么筛。
设
p
是一个质数,那么有
那么我们接下来要解决的问题是
h(a×p)
如何筛得。
总共分2种情况:
-
gcd(a,p)=1
,即
a,p
互质。这种情况很好处理:
h(a×p)=h(a)×h(p).
gcd(a,p)≠1 .此时又分两种小情况:
(1) a=x×pr. 那么, h(a×p)=h(x)×h(pr+1).
(2) a=pr. 这种情况下要稍微推一下式子:
已知:h(a)=h(pr)=1+∑i=1rpi×φ(pi).我们对它进行进一步转化:
h(a)=1+∑i=1rpi×(pi−pi−1)=1+(p−1)∑i=1rp2i−1.那么同理:
h(a×p)=h(pr+1)=1+(p−1)∑i=1r+1p2i−1.对于 h(pr) ,我们可以发现如下的关系:
h(pr)−1=(p−1)∑i=1rp2i−1
至此我们得到了 h(pr) 与 h(pr+1) 的关系,也就是 h(a) 与 h(a×p) 的关系。
整理一下:
至此,线性筛筛出所有的 h(n) ,对于每一个询问, Ans=(h(n)+1)×n2 可以 O(1) 计算。总复杂度 O(n+T).
代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
#define For(i,j,k) for(register ll i=j; i<=(ll)k; ++i)
#define Forr(i,j,k) for(register ll i=j; i>=(ll)k; --i)
#define INF 0x3f3f3f3f
using namespace std;
const ll maxn = 1000000+5;
ll T, n, tot;
ll pri[maxn/5], phi[maxn];
bool vis[maxn];
inline void read(ll &x){
x = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = (x<<3)+(x<<1)+(c^48), c = getchar();
}
inline void init(){
phi[1] = 1;
For(i, 2, maxn-1){
if(!vis[i]){
pri[++tot] = i;
phi[i] = (i-1) * i + 1;
}
for(ll j=1,x; j<=tot&&(x=i*pri[j])<maxn; ++j){
vis[x] = true;
if(i%pri[j] == 0){
ll ii = i, jj = pri[j];
while(ii%pri[j] == 0){
ii /= pri[j];
jj *= pri[j];
}
if(ii > 1) phi[x] = phi[ii] * phi[jj];
else phi[x] = phi[i] * pri[j]*pri[j] - pri[j]+1;
break;
}
else phi[x] = phi[i]*phi[pri[j]];
}
}
}
int main(){
init();
read(T);
while(T --){
read(n);
printf("%lld\n", 1ll * (phi[n]+1 >> 1) * n);
}
return 0;
}
—————————————-
小结
这一题的解决关键在于中间那个欧拉函数的结论,利用那个结论可以把式子化成能够用数论函数解决的形式。之后的重点是推导 h(n) 如何通过线性筛筛出来,情况的考虑并不复杂,但是式子不是很好推。总之,这是一道练习推式子以及筛法的好题。
—————————————-
——wrote by miraclejzd.