题意:求(1,b)区间和(1,d)区间里面gcd(x, y) = k的数的对数(1<=x<=b , 1<= y <= d)。
解法1:b和d分别除以k之后的区间里面,只需要求gcd(x, y) = 1就可以了,这样子求出的数的对数不变。这道题目还要求1-3 和 3-1 这种情况算成一种,因此只需要限制x小于y就可以了,只需要枚举x,然后确定另一个区间里面有多少个y就可以了。因此问题转化成为区间(1, d)里面与x互素的数的个数。先求出x的所有质因数,因此(1,d)区间里面是x的质因数倍数的数都不会与x互素,因此,只需要求出这些数的个数,减掉就可以了。如果w是x的素因子,则(1,d)中是w倍数的数共有d/w个。
容斥原理: 所有不与x互素的数的个数= 1个因子倍数的个数 - 2个因子乘积的倍数的个数 + 3个……
复杂度O(nlogn)我记得PO爷在他的莫比乌斯反演的论文中就提到过这种nlogn的方法,那个时候还没想明白,orz太弱啦
//HDU 1695 312ms
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
vector <int> pri[maxn];
void pre_init() //存每一个数的质因数
{
for(int i = 2; i < 100010; i++){
int now = i;
for(int j = 2; j*j <= now; j++){
if(now % j == 0){
pri[i].push_back(j);
while(now%j == 0) now /= j;
}
if(now == 1) break;
}
if(now > 1) pri[i].push_back(now);
}
}
int cal(int x, int tot)
{
int ans = 0;
for(int i = 1; i < (1<<pri[x].size()); i++){
int num = 0;
int tmp = 1;
for(int j = 0; j < (int)pri[x].size(); j++){
if((i>>j)&1){
num++;
tmp *= pri[x][j];
}
}
if(num%2==1) ans += tot/tmp;
else ans -= tot/tmp;
}
return tot - ans;
}
int main()
{
pre_init();
int t, ks = 0;
scanf("%d", &t);
while(t--){
int a, b, c, d, k;
scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
if(k == 0){
printf("Case %d: 0\n", ++ks);
continue;
}
b /= k;
d /= k;
if(b < d) swap(b, d);
long long ans = 0;
for(int i = 1; i <= b; i++){
ans += cal(i, min(i, d));
}
printf("Case %d: %lld\n", ++ks, ans);
}
return 0;
}
解法2:裸的莫比乌斯反演,但是这里统计方案的时候需要把重复的去掉。
复杂度是O(n^0.5)
//HDU 1695 0ms
#include <bits/stdc++.h>
using namespace std;
using namespace std;
const int maxn = 2e5+10;
int vis[maxn];
int prime[maxn];
int cnt;
int mu[maxn];
int sum[maxn];
void init()
{
memset(vis,0,sizeof(vis));
cnt=0;
mu[1]=1;
for(int i=2;i<maxn;i++)
{
if(!vis[i])
{
prime[cnt++]=i;
mu[i]=-1;
}
for(int j=0;j<cnt&&i*prime[j]<maxn;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j])
mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]]=0;
break;
}
}
}
sum[0]=0;
for(int i=1;i<maxn;i++)
sum[i]=sum[i-1]+mu[i];
}
int main()
{
int a,b,c,d,k;
init();
int T,ca=1;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("Case %d: ",ca++);
if(k==0)
{
printf("0\n");
continue;
}
b=b/k;
d=d/k;
if(b>d)
swap(b,d);
long long ans1=0;
int last;
for(int i=1;i<=b;i=last+1)
{
last=min(b/(b/i),d/(d/i));
ans1+=(long long)(sum[last]-sum[i-1])*(b/i)*(d/i);
}
long long ans2=0;
for(int i=1;i<=b;i=last+1)
{
last=b/(b/i);
ans2+=(long long)(sum[last]-sum[i-1])*(b/i)*(b/i);
}
long long ans=ans1-ans2/2;
printf("%lld\n",ans);
}
return 0;
}