额,很痛苦,看了一篇一篇的博客,总算是能说出来个东南西北了,但是肯定还没融会贯通,先记录一下把,就不提证明了。
首先要根据题意定义出函数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 的博客,仅供学习交流使用)
所以我们就可以进行反演了,之前呢,我们还能再进行一次简化,就是我们要求得是多少对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<iostream>
#include<string.h>
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;
}