[POI2007] ZAP-Queries
题目描述
Byteasar the Cryptographer works on breaking the code of BSA (Byteotian Security Agency). He has alreadyfound out that whilst deciphering a message he will have to answer multiple queries of the form"for givenintegers aa, bb and dd, find the number of integer pairs (x,y)(x,y) satisfying the following conditions:
1le xle a1≤x≤a,1le yle b1≤y≤b,gcd(x,y)=dgcd(x,y)=d, where gcd(x,y)gcd(x,y) is the greatest common divisor of xx and yy".
Byteasar would like to automate his work, so he has asked for your help.
TaskWrite a programme which:
reads from the standard input a list of queries, which the Byteasar has to give answer to, calculates answers to the queries, writes the outcome to the standard output.
FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d。作为FGD的同学,FGD希望得到你的帮助。
输入输出格式
输入格式:
The first line of the standard input contains one integer nn (1le nle 50 0001≤n≤50 000),denoting the number of queries.
The following nn lines contain three integers each: aa, bb and dd(1le dle a,ble 50 0001≤d≤a,b≤50 000), separated by single spaces.
Each triplet denotes a single query.
输出格式:
Your programme should write nn lines to the standard output. The ii'th line should contain a single integer: theanswer to the ii'th query from the standard input.
输入输出样例
输入样例#1:
2
4 5 2
6 4 3
输出样例#1:
3
2
Solution
预备知识:莫比乌斯反演,整除分块
不会的看这位dalao的博客莫比乌斯反演
本蒟蒻的整除分块
根据题意
下面就是反演
但是这样枚举还是(O(n^2)),所以我们换一个变量枚举,把最后一个求和提到前面,因为p既是i的因子又是j的因子,所以枚举范围就是(min(a/d,b/d)),那么继续推公式
如果对于后面的式子不理解,可以这么看,令(x=a/d,y=b/d)
(p)是(x,y)的一个因子,在(x)的范围内有(lfloorfrac{x}{p}
floor)个(p)的倍数,对于(y)同理,所以每个因子(p)都有(lfloorfrac{x}{p}
floorlfloorfrac{y}{p}
floor)的贡献
而对于后面的两个求和我们是可以用前缀和预处理出来的,这个时候是可以做到(O(n))了,但是由于多组数据,所以我们发现,对于一段连续的p,因为a和b的值是确定的,所以(lfloorfrac{a}{p imes d} floorlfloorfrac{b}{p imes d} floor)的值也是确定的,这中间有许多重复的值,那么我们就可以使用整除分块优化到(O(sqrt n))
(有错误欢迎指出)
Code
#include<bits/stdc++.h>
#define lol long long
#define il inline
#define rg register
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)>(b)?(a):(b)
using namespace std;
const int N=5e4+10;
void in(int &ans)
{
ans=0; int f=1; char i=getchar();
while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0', i=getchar();
ans*=f;
}
int n,m,d,tot,ans,T;
int mu[N],sum[N],prime[N];
bool vis[N];
il void get_mu() {
mu[1]=1;
for(int i=2;i<=N-10;i++) {
if(!vis[i]) prime[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot && prime[j]*i<=N-10;j++) {
vis[i*prime[j]]=1;
if(i%prime[j]==0) break;
else mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=N-10;i++) sum[i]=sum[i-1]+mu[i];
}
int main()
{
in(T); get_mu();
while(T--) {
in(n),in(m),in(d); int nn=n/=d,mm=m/=d,ans=0;
for(rg int i=1,pos,p=Min(n,m);i<=p;i=pos+1) {
pos=Min(n/(n/i),m/(m/i));
ans+=(sum[pos]-sum[i-1])*(nn/i)*(mm/i);
}
printf("%d
",ans);
}
return 0;
}