[BZOJ3518] 点组计数

题面

显然

横着或竖着共有(n*(_3^m)+m*(_3^n))

斜着的方案数为

[sum_{i=1}^{n}sum_{j=1}^{m}{(gcd(i,j)-1)(n-i)(m-j)} ]

就是枚举两个端点

令其中一个为原点

另一个为((i,j))

(gcd(i,j)=d)

所以最靠近原点的整点是((frac{i}{d},frac{j}{d}))

所以从这个点到((i,j))(frac{i}{(frac{i}{d})}=d)个整点

除去((i,j))本身

那么他们之间有(gcd(i,j)-1)个整点

然后可以上下左右平移这个线段

答案就是((gcd(i,j)-1)(n-i)(m-j))

考虑如何计算这个式子

(-1)提出来再反演即可

[sum_{i=1}^{n}sum_{j=1}^{m}{gcd(i,j)(n-i)(m-j)} \=sum_{i=1}^{n}sum_{j=1}^{m}{sum_{d|gcd(i,j)}{varphi(d)}(n-i)(m-j)} \=sum_{d=1}^{n}varphi(d)sum_{i=1}^{lfloor frac{n}{d} floor}{(n-i*d)}sum_{j=1}^{lfloor frac{m}{d} floor}{(m-j*d)} \=sum_{d=1}^{n}varphi(d)*(n*lfloor frac{n}{d} floor-d*frac{lfloor frac{n}{d} floor*(lfloor frac{n}{d} floor-1)}{2})*(m*lfloor frac{m}{d} floor-d*frac{lfloor frac{m}{d} floor*(lfloor frac{m}{d} floor-1)}{2}) ]

因为提了一个$-1 $

答案先减去(frac{(n-1)*n}{2}+frac{(m-1)*m}{2})

因为端点顺序不固定

答案乘(2)

最后再加上(n*(_3^m)+m*(_3^n))即可

#include "bits/stdc++.h"

using namespace std;

#define ll long long

const int p=1e9+7;
const int N=1e6+7;

int tot;
int pri[N],phi[N];

inline void init(int n){
	phi[1]=1;
	for(int i=2;i<=n;++i){
		if(!phi[i])pri[++tot]=i,phi[i]=i-1;
		for(int j=1,tmp;j<=tot&&(tmp=i*pri[j])<=n;++j){
			if(i%pri[j]==0){
				phi[tmp]=phi[i]*pri[j];
				break;
			}
			phi[tmp]=phi[i]*phi[pri[j]];
		}
	}
}

inline ll s(ll x){
	return (x*(x+1)>>1)%p;
}

inline ll calc(ll n,ll d){
	return ((n/d*n-d*s(n/d))%p+p)%p;
}

inline ll C3(ll x){
	return (x<3?0:x*(x-1)*(x-2)/6)%p;
}

int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	if(n>m)swap(n,m);
	ll ans=0;
	init(n);
	for(int d=1;d<=n;++d){
		(ans+=phi[d]*calc(n,d)%p*calc(m,d))%=p;;
	}
	(ans+=p-s(n-1)*s(m-1)%p)%p;
	printf("%lld
",(ans*2%p+m*C3(n)%p+n*C3(m)%p)%p);
	return 0;
}

原文地址:https://www.cnblogs.com/yicongli/p/10165502.html