BZOJ 2820 YY的GCD (莫比乌斯反演+前缀和+二维分块)*

神犇YY虐完数论后给傻×kAc出了一题

给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对

kAc这种傻×必然不会了,于是向你来请教……

多组输入

Input

第一行一个整数T 表述数据组数

接下来T行,每行两个正整数,表示N, M

Output

T行,每行一个整数表示第i组数据的结果

Sample Input

2
10 10
100 100

Sample Output

30
2791

Hint

T = 10000

N, M <= 10000000

#include<bits/stdc++.h>
using namespace std;

#define debug puts("YES");
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define read(x,y) scanf("%d%d",&x,&y)

#define ll long long
/*
题目意思不解释:

正常的莫比乌斯思维就是:
sigma p(p是质数) n/(i*p)*m/(i*p)*u(i),i枚举到min(n,m)。
但还可以优化,式子可以继续化简,
详见:https://blog.csdn.net/consciousman/article/details/77888386

最后奇妙的数学体现在求前缀和的函数上,用到了贡献的思想。

*/

const int  maxn =1e7+5;
const int mod=998244353;

int prim[maxn],miu[maxn],cnt=0;
int s[maxn];
bool vis[maxn];///莫比乌斯函数的题目前缀和。
void sieve(int N)
{
    cnt=0;miu[1]=1;
    for(int i=2;i<=N;i++)
    {
        if(!vis[i])
        {
            prim[cnt++]=i;
            miu[i]=-1;
        }
        for(int j=0;j<cnt;j++)
        {
            ll k=i*prim[j];
            if(k>N) break;
            vis[k]=1;
            if(i%prim[j]) miu[k]=-miu[i];
            else break;
        }
    }


    memset(s,0,sizeof(s));
    for(int i=0;i<cnt;i++)
        for(int j=prim[i],k=1;j<=N;j+=prim[i],k++)
            s[j] += miu[k];///贡献的思想
    for(int i=1;i<=N;i++) s[i]+=s[i-1];///求个前缀和,方便分块答案的计算
}

ll n,m;

int main()
{
    sieve(1e7);
    int t;scanf("%d",&t);
    while(t--)
    {
        scanf("%lld%lld",&n,&m);
        if(n>m) swap(n,m);///n是最小的,枚举n
        ll ans=0;
        for(ll i=1,last;i<=n;i=last+1)
        {
            last=min(n/(n/i),m/(m/i));
            ans+=(n/i)*(m/i)*(s[last]-s[i-1]);///二维分块
        }
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37451344/article/details/81937331