2301: [HAOI2011]Problem b
Time Limit: 50 Sec Memory Limit: 256 MB
Submit: 1756 Solved: 755
[Submit][Status][Discuss]
Description
对于给出的n个询问,每次求有多少个数对(x,y),满足a≤x≤b,c≤y≤d,且gcd(x,y) = k,gcd(x,y)函数为x和y的最大公约数。
Input
第一行一个整数n,接下来n行每行五个整数,分别表示a、b、c、d、k
Output
共n行,每行一个整数表示满足要求的数对(x,y)的个数
Sample Input
2
2 5 1 5 1
1 5 1 5 2
Sample Output
14
3
HINT
100%的数据满足:1≤n≤50000,1≤a≤b≤50000,1≤c≤d≤50000,1≤k≤50000
学了反演先来写这个题233
既然在做之前已经知道了是反演,那就往反演那两个函数上想没的说。。。
先用容斥原理,拆分询问来应对下界a,c
设f(i)为gcd(x,y)=i(1≤x≤n,1≤y≤m)的数对数目,
和他对应的F(i)为gcd(x,y)是i的倍数的数对的数目
那么f(i)=∑i|aμ(ai)F(a)=∑i|aμ(ai)?na??ma?
然后就得到了O(n2)的暴力反演
很显然过不了这个题。。。
怎么优化?
我们发现?na?最多有n√个取值
所以?na??ma?最多有
2(n√+m??√)个取值。
因此先对μ维护前缀和
然后在处理四个询问时候都只需要枚举2(n√+m??√)个数而不需要像以前那样暴力枚举了
最后O(nn√)过此题。
枚举除法的取值这种方法在莫比乌斯反演的应用当中非常常用,且代码并不难写//这是PoPoQQQ神犇在课件里的话。
要做好反演的题这种做法也是不可少的呢嗯。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXN 51000
using namespace std;
int T;
int a,b,c,d,k;
bool not_prime[MAXN];
int num;
int prime[MAXN];
int mu[MAXN]={0,1};
int prev[MAXN];
int F[MAXN],f[MAXN];
void in(int &x)
{
char ch=getchar();
x=0;
while (!(ch>=‘0‘&&ch<=‘9‘)) ch=getchar();
while (ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar();
}
void check_prime()
{
for (int i=2;i<=50000;i++)
{
if(!not_prime[i])
prime[++num]=i,mu[i]=-1;
for (int j=1;j<=num&&prime[j]*i<=50000;j++)
{
not_prime[i*prime[j]]=1;
if (i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
}
int getnum(int x,int y)
{
int last=0,f=0;
x/=k;y/=k;
for (int i=1;i<=min(x,y);i=last+1)
{
last=min(x/(x/i),y/(y/i));
f+=(prev[last]-prev[i-1])*(x/i)*(y/i);
}
return f;
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
check_prime();
for (int i=1;i<=50000;i++) prev[i]=prev[i-1]+mu[i];
in(T);
while (T--)
{
in(a);in(b);in(c);in(d);in(k);
a--;c--;
int ans=0;
ans=getnum(b,d)-getnum(a,d)-getnum(c,b)+getnum(a,c);
printf("%d\n",ans);
}
}