[BZOJ2820]YY的GCD(莫比乌斯反演+线性筛)

=== ===

这里放传送门

=== ===

题解

记得当初懵(fei)懂(chang)无(sha)知(bi)的时候把这个题当一个无脑水题写了一发结果T的死惨死惨的。。。当时直接O(n)回答询问了也不想想能不能过。。然后现在这个题就变成了ATP的反演入门题_ (:з」∠)_

题目要求

pi=1Nj=1M[(i,j)=p]

F(k) 为满足 1iN1jM 并且 gcd(i,j)=k 的数对个数, f(k) 为满足 1iN1jM 并且公约数为 k ,即 k|gcd(i,j) 的数对个数,显然就有关系是 f(k)=k|dF(d) ,并且 f(k)=NkMk

利用莫比乌斯反演公式可以得到 F(k)=k|dμ(dk)f(d) 。要求的实际上就是 pF(p) ,替换一下可以得到

pp|Tμ(Tp)f(T)

T 最大枚举到 min(N,M) ,那么直接枚举 T 就有
T=1NNTMTpμ(Tp)

化到这一步可以看出设 g(T)=p,p|Tμ(Tp) ,如果能求出 g(T) 的前缀和,我们就可以用分块枚举除法的方法在 O(n+m) 的时间复杂度内回答每个询问。

g(T) 实际上有两个方法:第一个是枚举 1..N 范围内所有的质数,用类似埃式筛法的方法枚举每个质数的倍数来更新,这样的复杂度是 O(nloglogn) 的;还有更快一点的方法是用线性筛。

用线性筛求一个函数一般分三步:

  1. T 为质数的函数值。这个时候 g(T)=μ(T/T)=1
  2. 在已知 g(T) 的情况下,为 T 新增一个原来没有的质因子 prm Tprm 的函数值。这个时候枚举的质因子新增了一个 prm 。那么分两种情况讨论:如果枚举的质因子是原来就有的那些,也就是不包括 prm ,那么后面那个 μ 函数就相当于在原来的基础上多乘了一个 prm ,那也就是都取反了;如果枚举的质因子是 prm ,那么分子上新增的 prm 会被下面约掉,也就剩下了 μ(T) 。综上所述,此时有 g(Tprm)=μ(T)g(T)
  3. 在已知 g(T) 的情况下,为 T 新增一个原来已经有的质因子 prm Tprm 的函数值。这个时候枚举的质因子集合实际上没有变,但是仍然分两种情况讨论。如果枚举的质因子是除了 prm 以外的那些,因为 Tprm 里面 prm 的指数至少为2,也就是里面有一个平方因子。如果分母不带 prm 的话就根本消不掉这个平方因子,就导致 μ 值为0,也就是这一类对答案没有贡献。如果枚举的质因子是 prm ,那么就可以累加一个 μ(T) 。综上所述,此时有 g(Tprm)=μ(T)

这样的话我们就能用 O(n+T(n+m)) 的复杂度解决这个题了。



实际上ATP一开始不是这么做的。。仍然用了愚蠢的xjb推式子方法。。
如果有人有兴趣的话就溜一眼好了。。

原始式子为

pi=1Nj=1M[(i,j)=p]

i=pij=pj ,从枚举 i j 转向枚举 i j
pi=1Npj=1Mp[(i,j)=1]

利用公式 [n=1]=d|nμ(d) 进行替换:
pi=1Npj=1Mpd=1Np[d|i][d|j]μ(d)

将式子进行一下变换,把那些看起来可以一块算的东西扔到一起去:
pd=1Npμ(d)i=1Np[d|i]j=1Mp[d|j]

式子 i=1n[d|i] 表示的是 1..n 范围内有多少 d 的倍数,这个值就等于 nd
pd=1Npμ(d)NpdMpd

T=pd ,那么 d=Tp ,并且因为d的上限到 Np ,那么 T 的枚举上限应该到 N
T=1NNTMTp,p|Tμ(Tp)

这样就用了一种极其麻烦的方法得到了和上面一样的结论。。

代码

#include
#include
#include
using namespace std;
int T,n,m,prm[10000010],mu[10000010],tail;
long long ans,sum[10000010];
bool ext[10000010];
long long Mul(long long x,long long y){return x*y;}
void get_prime(int N){
    mu[1]=1;
    for (int i=2;i<=N;i++){
        if (ext[i]==false){
            prm[++prm[0]]=i;
            mu[i]=-1;sum[i]=1;
        }
        for (int j=1;j<=prm[0];j++){
            if ((long long)i*prm[j]>N) break;
            ext[i*prm[j]]=true;
            if (i%prm[j]==0){
                sum[i*prm[j]]=mu[i];
                break;
            }
            else{
                mu[i*prm[j]]=-mu[i];
                sum[i*prm[j]]=mu[i]-sum[i];
            }
        }
    }
    for (int i=1;i<=N;i++) sum[i]+=sum[i-1];
}
int main()
{
    scanf("%d",&T);
    get_prime(10000000);
    for (int wer=1;wer<=T;wer++){
        scanf("%d%d",&n,&m);
        if (n>m) swap(n,m);
        ans=0;
        for (int i=1;i<=n;i=tail+1){
            tail=min(n,min(n/(n/i),m/(m/i)));
            ans=ans+Mul(n/i,m/i)*(sum[tail]-sum[i-1]);
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

偏偏在最后出现的补充说明

如果科学地设出一个函数,用这个函数来做反演,就比xjb推式子要简单很多。。

如果出现了类似 Nid 这样的东西,考虑一下能不能把 id 看成一个整体,从枚举倍数转为枚举因数。这样往往可以构造出能够线筛的函数,这也是很多反演题目的常见思路。

如果发现题目有好多化不掉的 ,试试能不能把里面的东西xjb乱移动一下,把能够一起算的东西凑到一起去,这样有时候可以做到把嵌套的循环变成并列的循环。

线性筛三步走的思路非常常用,但是有些时候也不仅仅局限于这个思路,还是要观察要筛的函数本身的性质。

你可能感兴趣的:(BZOJ,烧脑的数论)