P3166 [CQOI2014]数三角形(组合数学)

整点三角形个数。

正难则反,求出总方案和共线三角形方案数即可。

总方案: C ( ( n + 1 ) ( m + 1 ) , 3 ) C((n+1)(m+1),3) C((n+1)(m+1),3)

共线分三种情况:水平、垂直、倾斜。

水平: ( n + 1 ) C m + 1 3 (n+1)C_{m+1}^3 (n+1)Cm+13

同理垂直: ( m + 1 ) C n + 1 3 (m+1)C_{n+1}^3 (m+1)Cn+13

考虑倾斜如何求,因为正斜率和反斜率是对称的。

所以只需算出正的然后乘以2即可。

因为两条直角边唯一确定斜边。

所以可以枚举两直角边长度 i , j i,j i,j

然后以两个端点作为三角形的两端点,在斜边上再找一个点的方案。

结论是: g c d ( i , j ) − 1 gcd(i,j)-1 gcd(i,j)1

考虑斜边直线: g = g c d ( i , j ) , i = u g , j = v g , y = u v x g=gcd(i,j),i=ug,j=vg,y=\dfrac{u}{v}x g=gcd(i,j),i=ug,j=vg,y=vux

v ∣ x v|x vx

这样的 x x x的个数是: j v − 1 = g − 1 = g c d ( i , j ) − 1 \dfrac{j}{v}-1=g-1=gcd(i,j)-1 vj1=g1=gcd(i,j)1

减1是不包括两端。

所以这一部分答案就是: ∑ i = 1 n ∑ j = 1 m ( n − i + 1 ) ( m − j + 1 ) ( g c d ( i , j ) − 1 ) \sum\limits_{i=1}^n\sum\limits_{j=1}^m(n-i+1)(m-j+1)(gcd(i,j)-1) i=1nj=1m(ni+1)(mj+1)(gcd(i,j)1)

事实上还可以用欧拉反演进行化简。

下面参考某大佬。

P3166 [CQOI2014]数三角形(组合数学)_ios

P3166 [CQOI2014]数三角形(组合数学)_ios_02

P3166 [CQOI2014]数三角形(组合数学)_组合数学_03

#include <cstdio>
#include <iostream> 
using namespace std;
const int maxn = 1005;
int n, m, p[maxn], phi[maxn], tot;
bool mark[maxn];
long long ans;
long long C(long long x) { return x * (x-1) * (x-2) / 6; }
void sieve(int n) {
	phi[1] = 1;
	for(int i = 2; i <= n; ++i) {
		if(!mark[i]) p[++tot] = i, phi[i] = i - 1;
		for(int j = 1; j <= tot and p[j]*i <= n; ++j) {
			mark[p[j]*i] = true;
			if(i % p[j]) phi[p[j]*i] = phi[i] * (p[j]-1);
			else { phi[p[j]*i] = phi[i] * p[j]; break; }
		}
	}
}
int main() {
	scanf("%d %d", &n, &m);
	if(n > m) swap(n, m);
	sieve(n);
	for(int d = 2, x, y; d <= n; ++d)//记得 ans 要乘以 2,这里直接除以 2 就行了不用除以 4
		ans += (long long)phi[d]*(n-d+n%d+2)*(n/d)*(m-d+m%d+2)*(m/d)/2;
	ans = C((n+1)*(m+1)) - (m+1)*C(n+1) - (n+1)*C(m+1) - ans;
	printf("%lld\n", ans);
}