容斥
题意不在说明。
解法
求x<=a,y<=b且gcd(x,y)=d,那么对式子除以个d,得到 x′<=ad,y′<=bd,gcd(x′,y′)=1
所以就可以利用容斥求。
那么方案数是多少呢?我们令a/d=a′,b/d=b′,那么答案应该就是a′⋅b′−a′2⋅b′2−a′3⋅b′3−……+a′6⋅b′6+……−……。
就是减去有一个公因子的个数,加上有两个的,也就是容斥原理的应用。
但是可以发现,如果我们这样做的话,每次询问的复杂度是O(N),所以会爆掉。
思考如何去优化,根据答案的形式,可以发现这其实是一个莫比乌斯函数。
所以答案可以转化成a′⋅b′−min(a′,b′)∑i=1a′i⋅b′i⋅mobius(i)。可见复杂度是O(N)。
考虑优化。我们将a′i拿出来看,就是a′1⋅a′2⋅……⋅a′n。虽然有n项相乘,但其中最多只有2√a′项,所以就可以将原来分成√n段,利用前缀和,理论上可以将求和一部分简化到O(√N)的复杂度。
证明
为什么要分成√n段呢?很好证明,将其分成两段a′1⋅a′2⋅a′√a′与a′√a′+1⋅……⋅a′n,前面只有只有sqrta′个不同答案,后面只有sqrta′中答案,所以总共就有2√a′
如何去求
这里有一个经典的函数,我们设g(x),他的值就是能够使ax=ag(x)成立的最大的数,也就是a除以一个数,值与ax相等,且最大的一个数,也就有ax>ag(x)+1。
这个g(x)=aax。
请注意,本文所有除法都带有向下取整。
证明ax=ag(x)。
因为有g(x)=aax,将分子中的向下取整去掉,那么分子变大整个式子变小,化简就得g(x)=x。那么g(x)>=x。所以ag(x)<=ax。那就只需证明aaax>=ax。将第二个取整符号去掉,分子变大,原式变小,化简成ax=ax,所以ag(x)>=ax。
即证。
证明ax>ag(x)+1
这个证明很有必要,不证的话就只代表一个,就不能保证复杂度为O(√N)。
先令a=kx+r,0<=r<x
那么原式就是k>aak+1<=>k⋅(ak+1)>a。
再令a=pk+q,0<=q<k。
带回刚刚的式子中,就是k⋅(p+1)>pk+q−>kp+k>pk+q−>k>q。
即证
那么所以,对于答案的求和,只需要跳√N次。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N=50010;
typedef long long ll;
int T;
int n;
int cnt;
int a,b,d;
int sum[N];
bool vis[N];
int pri[N],mobius[N];
void Mobius() {
mobius[1]=1;
for(int i=2; i<N; ++i) {
if(!vis[i]) {
pri[cnt++]=i;
mobius[i]=-1;
}
for(int j=0; pri[j]*i<N; ++j) {
int t=pri[j]*i;
vis[t]=1;
if(i%pri[j]==0) {
mobius[t]=0;
break;
}
mobius[t]=mobius[i]*-1;
}
}
for(int i=1; i<N; ++i) sum[i]=sum[i-1]+mobius[i];
return ;
}
int main() {
Mobius();
scanf("%d",&T);
while(T--) {
scanf("%d%d%d",&a,&b,&d);
a/=d,b/=d;
n=min(a,b);
ll res=0;
for(int l=1,r; l<=n; l=r+1) {
r=min(n,min(a/(a/l),b/(b/l)));
res+=(sum[r]-sum[l-1])*1ll*(a/l)*(b/l);
}
printf("%lld\n",res);
}
return 0;
}
大佬题解真的棒hh
写的很棒
博客有点小小的错误,但是问题不大a′⋅b′−∑i=1min(a′,b′)a′i⋅b′i⋅mobius(i) 这个应该是手误,大佬博客写的不错!多写点