[BZOJ]2226: [Spoj 5971] LCMSum 欧拉函数(或莫比乌斯反演)

Description
Given n, calculate the sum LCM(1,n) + LCM(2,n) + .. + LCM(n,n), where LCM(i,n) denotes the Least Common Multiple of the integers i and n.

题解:

做法一:
有这个结论就很简单了:小于等于n且与n互质的数的和,然后就可以 O(nlogn) 预处理出所有的答案。( n+n2+n3....+1 )是 O(nlogn) 的。
做法二:
莫比乌斯反演。
大力推式子:

ansn=i=1nlcm(n,i)=i=1nn×igcd(n,i)
枚举最大公约数 d ,设F(x) <=x <script type="math/tex" id="MathJax-Element-3873"><=x</script>且与 x 互质的数的和,很容易推出以下式子:
ansn=d|nF(nd)×n
然后考虑如何求 F(x)
F(x)=i=1x[gcd(x,i)==1?1:0]×i
然后就是莫比乌斯反演的基本变形:
F(x)=i=1xi×d|gcd(x,i)μ(d)
F(x)=i=1xi×d|xd|iμ(d)
然后将式子变为枚举 d ,考虑每个μ(d)对答案的贡献:
F(x)=d|xμ(d)×sum(d,xd×d,d)
上面的 sum(l,r,c) 表示首项为 l ,末项为r,公差为 c 的等差数列的和。
然后就可以通过枚举因数来预处理出F ans ,复杂度也是 O(nlogn) 的,但是多了点常数,竟然是20s卡过的……

代码(做法一):

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=1000010;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
int prime[Maxn>>1],len=0;
LL phi[Maxn],ans[Maxn],sum[Maxn];
bool mark[Maxn];
void pre()
{
    memset(mark,false,sizeof(mark));
    phi[1]=1;
    for(int i=2;i<=1000000;i++)
    {
        if(!mark[i]){prime[++len]=i;phi[i]=i-1;}
        for(int j=1;j<=len&&prime[j]*i<=1000000;j++)
        {
            mark[prime[j]*i]=true;
            if(i%prime[j]==0)
            {
                phi[prime[j]*i]=prime[j]*phi[i];
                break;
            }
            phi[prime[j]*i]=(prime[j]-1)*phi[i];
        }
    }
    sum[1]=1;
    for(int i=2;i<=1000000;i++)sum[i]=phi[i]*(LL)(i)/2LL;
    for(int i=1;i<=1000000;i++)
    for(int j=i;j<=1000000;j+=i)
    ans[j]+=sum[i];
    for(int i=1;i<=1000000;i++)ans[i]*=(LL)(i);
}
int main()
{
    pre();
    int T=read();
    while(T--)printf("%lld\n",ans[read()]);
}

代码(做法二):

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=1e6+10;
const int inf=1e6;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
int prime[Maxn],len=0,mu[Maxn];
bool mark[Maxn];
LL F[Maxn],ans[Maxn];
LL Sum(LL l,LL r,LL c)
{
    LL x=(r-l)/c+1;
    return (l+r)*x/2LL;
}
void pre()
{
    memset(F,0,sizeof(F));
    memset(ans,0,sizeof(ans));
    memset(mark,false,sizeof(mark));
    mu[1]=1;
    for(int i=2;i<=inf;i++)
    {
        if(!mark[i])prime[++len]=i,mu[i]=-1;
        for(int j=1;j<=len&&prime[j]*i<=inf;j++)
        {
            mark[prime[j]*i]=true;
            if(i%prime[j]==0){mu[prime[j]*i]=0;break;}
            mu[prime[j]*i]=-mu[i];
        }
    }
    for(int d=1;d<=inf;d++)
    for(int x=d;x<=inf;x+=d)
    F[x]+=(LL)(mu[d]*Sum(d,(x/d)*d,d));
    for(int d=1;d<=inf;d++)
    for(int x=d;x<=inf;x+=d)
    ans[x]+=F[x/d]*(LL)(x);
}
int main()
{
    pre();
    int T=read();
    while(T--)printf("%lld\n",ans[read()]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值