【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; }