[bzoj2226] LCMSum 数学+筛法

来源:互联网 发布:交行信用卡 知乎 编辑:程序博客网 时间:2024/06/05 07:07

题目链接:bzoj2226 .

—————————————-

概述

题目大意如下:
给定正整数n,求

i=1nlcm(i,n).

题目有多组数据。(T300000, n1000000)

lcm(x,y)x,y的最小公倍数。

—————————————-

分析

对于lcm(i,n),我们很显然可以把它转化成如下形式:

lcm(i,n)=i×ngcd(i,n).

对于某个i,我们令d=gcd(i,n)n=d×xi=d×y,那么xy是互质的。

所以,我们不妨枚举d,再枚举所有与x互质的y并统计答案,其中x=nd.

我们得到下式:

Ans = d|ny=1ndnd×y×d×[gcd(nd,y)=1] = nd|ny=1ndy×[gcd(nd,y)=1].

化到这里,问题就变成了:对于n所有的约数d,计算小于nd且与其互质的数的和,将结果×n就是答案了。

那么关键来了,所有与nd互质的数的和怎么求呢?

——————–

结论:
i=1ni×[gcd(i,n)=1] = n×φ(n)2.

特别地,n=1时值为1.

证明:

引理: 对于一个与n互质的数xnx也一定与n互质。

引理证明:设gcd(nx,n)=d,那么nxd一定是整数,即ndxd是整数。由于dn的约数,xn互质,所以xd是整数当且仅当d=1,所以gcd(nx,n)=d=1。引理证毕。

我们要求的是与n互质的所有数的和,即:

i=1ni×[gcd(i,n)=1].

根据引理,我们又有:

i=1ni×[gcd(i,n)=1] = i=1n2(i+(ni))×[gcd(i,n)=1] = ni=1n2[gcd(i,n)=1] = n×φ(n)2.

结论证毕。

——————–

有了这个结论就好办了,我们原来要求的是:

Ans=nd|ny=1ndy×[gcd(nd,y)=1]

现在我们可以将它转化为:

Ans = n(1+d|nnd×φ(nd)2) = n2(1+d|nd×φ(d)).

其中,加1是因为当d=1时本应该有值为1,然而若按上面的式子计算d=1时为0,所以要加1.

至此,有2种做法,一个是80分,一个是100分。


80分:

待求:

Ans = n2(1+d|nd×φ(d)).

我们可以线性筛出所有的φ(d),对于每个询问O(n)枚举n所有约数d并统计答案。总复杂度O(Tn).

代码

#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 0x3f3f3f3fusing 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分

待求:

Ans = n2(1+d|nd×φ(d)).

我们令h(n)=d|nd×φ(d),那么答案即是:

Ans=n2(1+h(n)).

我们观察一下h(n)=d|nd×φ(d),由于dφ(d)都是积性函数,所以h(n)也是积性函数。

既然h(n)是积性函数,那么就可以通过线性筛筛出来,我们思考一下怎么筛。

p是一个质数,那么有h(p)=p×(p1)+1.
那么我们接下来要解决的问题是h(a×p)如何筛得。

总共分2种情况:

  1. gcd(a,p)=1,即a,p互质。这种情况很好处理:
    h(a×p)=h(a)×h(p).
  2. 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×(pipi1)=1+(p1)i=1rp2i1.

    那么同理:

    h(a×p)=h(pr+1)=1+(p1)i=1r+1p2i1.

    对于h(pr),我们可以发现如下的关系:

    h(pr)1=(p1)i=1rp2i1

(h(pr)1)×p2=(p1)i=2r+1p2i1

 (h(pr)1)×p2+(p1)×p=(p1)i=1r+1p2i1

1+(h(pr)1)×p2+(p1)×p=h(pr+1)

   1+h(pr)×p2p=h(pr+1)

至此我们得到了h(pr)h(pr+1)的关系,也就是h(a)h(a×p)的关系。

整理一下:

h(a×p)=h(a)×h(p),h(x)×h(pr+1),h(a)×p2p+1,gcd(a,p)=1gcd(a,p)1, a=x×prgcd{a,p}1, a=pr

至此,线性筛筛出所有的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 0x3f3f3f3fusing 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.