[bzoj2226] LCMSum 数学+筛法

题目链接: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×x i=d×y ,那么 x y是互质的。

所以,我们不妨枚举 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 互质的数x nx 也一定与 n 互质。

引理证明:设gcd(nx,n)=d,那么 nxd 一定是整数,即 ndxd 是整数。由于 d n的约数, x n互质,所以 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 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分

待求:

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 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值