莫比乌斯反演!!!

额,很痛苦,看了一篇一篇的博客,总算是能说出来个东南西北了,但是肯定还没融会贯通,先记录一下把,就不提证明了。

首先要根据题意定义出函数f(n),g(n),这两个函数中你得知道一个函数的结果,利用已知的函数的值去推到另一个函数的值,谓之反演,以HDU1695为例,构造的思想是很巧妙地
f(n)为 有多少对(x,y)满足 gcd(x,y)== d 的倍数 。

g(n)为有多少对(x,y)满足 gcd(x,y)== n 。
这里面我们已知的是f(n)的值————》是(x/n) * (y / n)
如何反演推出我们想要的g(n)呢??
有如下定理:
  这里先给出莫比乌斯的两个公式 :  (以下图片摘自 ACdreamer 的博客,仅供学习交流使用)

                       

                       

莫比乌斯反演!!!_第1张图片

所以我们就可以进行反演了,之前呢,我们还能再进行一次简化,就是我们要求得是多少对x,y使得gcd(x,y) == k,就相当于gcd(x/k,y/k) == 1得取值了,所以忙前向后我们要求的就是f(1),根据公式

g(1) = mobi(1/n)f(1) + mobi(2/n)*f(2)……到哪里结束呢??直到min(b,d)

但是这样会有重复(1,2 和2,1)所以要进行去重

#include
#include
using namespace std;
const int maxn = 1e5 + 1e4;
int mobi[maxn];
int pri[maxn];
int mark[maxn];
void get_mobi()
{
    memset(mark,0,sizeof(mark));
    memset(mobi,0,sizeof(mobi));
    mobi[1] = 1;
    int tot = 0;
    for(int i = 2;i < maxn;i++)
    {
        if(!mark[i])
        {
            pri[++tot] = i;
            mobi[i] = -1;
        }
        for(int j = 1;j <= tot;j++)
        {
            int x = pri[j];
            if(x * i >= maxn)break;
            mark[i * x] = 1;
            if(i % x == 0)
            {
                mobi[i * x] = 0;
                break;
            }
            else
            {
                mobi[i * x] = -mobi[i];
            }
        }
    }
}
int main()
{
    int t,a,b,c,d,k;
    long long ret1,ret2;
    scanf("%d",&t);
    get_mobi();
    for(int cas = 1;cas <= t;cas++)
    {
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        printf("Case %d: ",cas);
        if(k == 0)
        {
            printf("0\n");
            continue;
        }
        ret1 = ret2 = 0;
        b /= k;
        d /= k;
        for(int i = 1;i <= min(b,d);i++)
        {
            ret1 += (long long)mobi[i]*(b / i)*(d / i);
        }
        for(int i = 1;i <= min(b,d);i++)
        {
            ret2 += (long long)mobi[i] * (min(b,d)/i) * (min(b,d)/i);
        }
        printf("%lld\n",ret1 - ret2 / 2);
    }
    return 0;
}







你可能感兴趣的:(算法入门)