BZOJ2820: YY的GCD(Mobius反演)

传送门

题意
x , y ,求 1ix,1jy gcd(i,j) 为质数的 (i,j) 对数。

题解
不妨设 n<m
首先有重要等式:

abc=abc

设p为质数。
很容易写出:

pni=1nj=1m[gcd(i,j)==p]=pni=1npj=1mp[gcd(i,j)==1]=pni=1npj=1mpd|id|jμ(d)

一种算法:
先在外层对 np mp 分块计算,在素数数组中,二分查找相同区间 O(log(nInn)) ,内部同样分块,处理 μ(i) 前缀和可以做到 O(n) 。得到一种 O(Tlog(nInn)nInnn) 的算法,会超时。

第二种算法:
对上述公式进一步变形:

pni=1npj=1mpd|id|jμ(d)=pndμ(d)npdnpd

设D=pd.得:

pndμ(d)npdnpd=pnp|Dμ(Dp)nDnD=Dnp|Dμ(Dp)nDnD

F(i)=pnμ(np) .得

Dnp|Dμ(Dp)nDnD=DnF(D)nDnD

1.考虑枚举质数暴力更新所有 F(i) (这是一个只有质数的调和级数,由高数知识这个复杂度是 O(nloglogn) ,证明过于繁琐,不再赘述)。

2.由于该函数是积性函数,可以在欧拉筛时线性筛除。

由于 O(nloglogn) 已经接近于 O(n) ,直接暴力就好了。

那么预处理后统计 F(i) 前缀和并分块计算便可在 O(Tn) 时间内解决。

  • Code:
#include
using namespace std;
const int Maxn=1e7;
typedef long long ll;

inline int read()
{
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}

int Prime[Maxn+50],pr[Maxn+50],tot,mu[Maxn+50];
ll F[Maxn+50];

inline void sieve()
{
    mu[1]=1;
    for(int i=2;i<=Maxn;i++)
    {
        if(!pr[i]){pr[i]=i;Prime[++tot]=i;mu[i]=-1;}
        for(int j=1;j<=tot;j++)
        {
            int k=i*Prime[j];
            if(k>Maxn)break;
            pr[k]=Prime[j];
            if(i%Prime[j]){mu[k]=-mu[i];}
            else
            {
                mu[k]=0;
                break;
            }
        }
    }
    for(int i=1;i<=tot;i++)
    {
        int p=Prime[i];
        for(int j=1;j*p<=Maxn;j++)F[j*p]+=mu[j];
    }
    for(int i=1;i<=Maxn;i++)F[i]+=F[i-1];
}

int main()
{
    sieve();
    int T=read();
    while(T--)
    {
        int n=read(),m=read();
        if(n>m)swap(n,m);
        int pos;
        ll ans=0;
        for(int bg=1;bg<=n;bg=pos+1)
        {
            pos=min(n/(n/bg),m/(m/bg));
            ans+=(F[pos]-F[bg-1])*(n/bg)*(m/bg);
        }
        printf("%lld\n",ans);
    }
}

你可能感兴趣的:(Mobius反演)