bzoj3701,大力莫比乌斯反演

注意到n只有10万,这可比某些动辄 1 0 10 10^{10} 1010的毒瘤题良心多了。
首先将 [ l , r ] [l,r] [l,r]变为求前缀和
设当前的距离限制为L,对于两个点 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2),考虑枚举一维的增量dx=abs(x1-x2)
则答案为 ( n − d x + 1 ) ∑ j = 1 d x 2 + j 2 < = L [ g c d ( d x , j ) = = 1 ] ( m − j + 1 ) (n-dx+1)\sum_{j=1}^{dx^2+j^2<=L}[gcd(dx,j)==1](m-j+1) (ndx+1)j=1dx2+j2<=L[gcd(dx,j)==1](mj+1)
这个式子本身是 O ( n 2 ) O(n^2) O(n2)的,莫比乌斯反演一波就行了。
注意每个j的贡献与限制条件不直接相关,知道了dx后,可以很方便求它因子对自己的贡献。
但如果是对每个数去更新它的倍数,就很麻烦了,因为约束条件是由它的倍数,而不是它的本身确定的。
一开始被这个坑了好久,以后这种题还是要先想好在开始写,否则效率会很低。
顺便吐槽一下样例解释,“距离为2”应改成“距离为 2 \sqrt2 2
2018.11.27upd:该代码虽然能AC,但是第41行的i*i应改为1ll*i*i,否则会炸int,望读者注意。

#include
#include
#include
#include
using namespace std;
const int N=100005;
int n,m,l,r,mo,p[N],mu[N],xb,i,j,x,ans,u,v,y,z;
bool b[N];
vector d[N];
inline int ceIl(double x){
	return int(x)+(int(x)!=x);
}
int main(){
	scanf("%d%d%d%d%d",&n,&m,&l,&r,&mo);
	for(i=2,mu[1]=1;i<=m;++i){
		if(!b[i])p[++xb]=i,mu[i]=-1;
		for(j=1;j<=xb;++j){
			x=i*p[j];
			if(x>m+1)break;
			b[x]=1;
			if(i%p[j]==0)break;
			mu[x]=-mu[i];
		}
	}
	for(i=1;i<=n;++i)
		for(j=i;j<=n;j+=i)d[j].push_back(i);
	for(i=min(r-1,n);i;--i){
		v=sqrt(1ll*r*r-1ll*i*i);
		if(v>m)v=m;
		for(j=z=0;j<(int)d[i].size();++j){
			x=d[i][j];
			if(x>v)break;
			if(mu[x]){
				y=v/x;
				z=(z+1ll*mu[x]*(1ll*y*(m+1)%mo-1ll*x*(1ll*y*(y+1)>>1)%mo+mo)%mo+mo)%mo;
			}
		}
		ans=(ans+1ll*z*(n-i+1))%mo;
		//printf("%d\n",ans);
		if(l>i){
			u=sqrt(1ll*l*l-i*i-0.5);
			if(u>=m)u=m;
			for(j=z=0;j<(int)d[i].size();++j){
				x=d[i][j];
				if(x>u)break;
				if(mu[x]){
					y=u/x;
					z=(z-1ll*mu[x]*(1ll*y*(m+1)%mo-1ll*x*(1ll*y*(y+1)>>1)%mo+mo)%mo+mo)%mo;
				}
			}
			ans=(ans+1ll*z*(n-i+1)%mo)%mo;
		}
	}
	ans=(ans<<1)%mo;
	if(l==1)ans=(ans+1ll*n*(m+1)+1ll*(n+1)*m)%mo;
	printf("%d\n",ans);
	return 0;
}

你可能感兴趣的:(bzoj3701,大力莫比乌斯反演)