[HAOI2011][bzoj2301] Problem b [莫比乌斯反演+容斥原理+分块前缀和优化]

时间:2023-03-09 09:36:12
[HAOI2011][bzoj2301] Problem b [莫比乌斯反演+容斥原理+分块前缀和优化]

题面:

传送门

有洛谷就尽量放洛谷链接呗,界面友好一点

思路:

HDU1695比较像,但是这一回有50000组数据,直接莫比乌斯反演慢慢加的话会T

先解决一个前置问题:怎么处理a,c不是1的情况?

很简单,容斥原理搞之

我们设f(x,y)代表gcd(i,j)==e(1<=i<=x,1<=j<=y)的无序数对(i,j)的个数

那么本题答案相当于f(d,b)-f(c-1,b)-f(a-1,d)+f(a-1,c-1)

再来看反演超时的问题

我们注意到原反演过程中,f(1)==mu(i)*(d/i)*(b/i)

(对为什么这么做不太清楚的同学可移步上面HDU1695的那个链接)

对于后两项,在i很大的时候其实他们的值是基本不变动的,变化的只有mu[i]

那么我们可以利用这个过程

每一次,我们搜寻当前节点i的下一个“后两项的乘积改变了的”节点j

j的求法是min(d/(d/i).b/(b/i)),就是反过来求变化区间的大小,i越大,变化需要的时间越久

然后我们预处理mu的时候同时把mu的前缀和算出来,在上述情况下把(d/i)*(b/i)的值乘上sum[j]-sum[i-1]

循环结束以后,把i变成j+1,然后开始下一个循环,直到i的值超过min(b,d)

这就是莫比乌斯反演中的分块前缀和优化

顺便说一下,对于欧拉函数也可以利用这个优化,具体可以看这篇博客

Code:

 #include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
bool vis[];int mu[],pri[],cnt=,sum[];
void init(){
int i=,j,k;
mu[i]=sum[i]=;
for(i=;i<=;i++){
if(!vis[i]) pri[++cnt]=i,mu[i]=-;
for(j=;j<=cnt;j++){
k=i*pri[j];if(k>) break;
vis[k]=;
if(i%pri[j]==){mu[k]=;break;}
else mu[k]-=mu[i];
}
sum[i]=sum[i-]+mu[i];
}
}
inline int read(){
int re=,flag=;char ch=getchar();
while(ch>''||ch<''){
if(ch=='-') flag=-;
ch=getchar();
}
while(ch>=''&&ch<='') re=(re<<)+(re<<)+ch-'',ch=getchar();
return re*flag;
}
inline void swap(int &l,int &r){l^=r;r^=l;l^=r;}
ll f(int l,int r){
if(l>r) swap(l,r);ll re=,i,j=;
for(i=;i<=l;i=j+){
j=min(l/(l/i),r/(r/i));
re+=(ll)(sum[j]-sum[i-])*(l/i)*(r/i);
}
// cout<<"f "<<l<<ends<<r<<ends<<re<<endl;
return re;
}
int main(){
int a,b,c,d,e,T=read();
init();
while(T--){
a=read();b=read();c=read();d=read();e=read();
if(!e){printf("%d\n",);continue;}
b/=e;d/=e;a--;c--;a/=e;c/=e;
printf("%lld\n",f(b,d)-f(a,d)-f(c,b)+f(a,c));
}
}