【bzoj3518】点组计数 欧拉函数(欧拉反演)

题目描述

平面上摆放着一个n*m的点阵(下图所示是一个3*4的点阵)。Curimit想知道有多少三点组(a,b,c)满足以a,b,c三点共线。这里a,b,c是不同的3个点,其顺序无关紧要。(即(a,b,c)和
(b,c,a)被认为是相同的)。由于答案很大,故你只需要输出答案对1,000,000,007的余数就可以了。

输入

有且仅有一行,两个用空格隔开的整数n和m。

输出

有且仅有一行,一个整数,表示三点组的数目对1,000,000,007的余数。(1,000。000。007是质数)

样例输入

3 4

样例输出

2 0


题解

欧拉函数(欧拉反演)

先单独考虑横着的和竖着的,答案分别为 $m·C_n^3$ 和 $n·C_m^3$ 。

然后考虑斜着的:设第一个点和第三个点横坐标差为 $i$ ,纵坐标差为 $j$ ,那么它们中间就有 $\gcd(i,j)-1$ 个点,所以第二个点的个数就是 $\gcd(i,j)-1$ ;又因为这样的矩形有 $(n-i)(m-j)$ 个,每个矩形有2个,因此总个数就是 $2(n-i)(m-j)\gcd(i,j)$ 。

因此斜着的答案就是:

$\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^{m-1}2(n-i)(m-j)\gcd(i,j)=2\sum\limits_{d=1}^{min(n-1,m-1)}\varphi(d)\sum\limits_{i=1}^{\lfloor\frac {n-1}d\rfloor}(n-di)\sum\limits_{j=1}^{\lfloor\frac {m-1}d\rfloor}(m-dj)$

快筛 $\varphi$ ,枚举 $d$ ,后面的两个 $\sum$ 用等差数列求和公式 $O(1)$ 求出。

时间复杂度 $O(n)$ 

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 50010
#define mod 1000000007
using namespace std;
typedef long long ll;
int phi[N] , prime[N] , tot , np[N];
int main()
{
	int n , m , i , j;
	ll ans;
	scanf("%d%d" , &n , &m) , ans = ((ll)n * (n - 1) * (n - 2) / 6 % mod * m + (ll)m * (m - 1) * (m - 2) / 6 % mod * n) % mod;
	if(n > m) swap(n , m);
	n -- , m -- ;
	phi[1] = 1;
	for(i = 2 ; i <= n ; i ++ )
	{
		if(!np[i]) phi[i] = i - 1 , prime[++tot] = i;
		for(j = 1 ; j <= tot && i * prime[j] <= n ; j ++ )
		{
			np[i * prime[j]] = 1;
			if(!(i % prime[j]))
			{
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			else phi[i * prime[j]] = phi[i] * phi[prime[j]];
		}
	}
	for(i = 1 ; i <= n ; i ++ )
		ans = (ans + (ll)(n - i + 1 + n - n / i * i + 1) * (n / i) / 2 % mod * (m - i + 1 + m - m / i * i + 1) % mod * (m / i) % mod * phi[i]) % mod;
	printf("%lld\n" , (ans - (ll)n * (n + 1) / 2 % mod * m % mod * (m + 1) % mod + mod) % mod);
	return 0;
}

 

 

posted @ 2017-12-25 19:35  GXZlegend  阅读(501)  评论(0编辑  收藏  举报