【洛谷P3166】数三角形
题目
题目链接:https://www.luogu.com.cn/problem/P3166
给定一个 \(N\times M\) 的网格,请计算三点都在格点上的三角形共有多少个。注意三角形的三点不能共线。
思路
考虑总方案数减去三点共线的方案数。枚举每一个点 \((x,y)\),然后再枚举在 \((x,y)\) 左下角的一个点 \((k,l)\),那么在这两个点之间的点有 \(\gcd(i-k,j-l)-1\) 个。
但是当这三个点所构成直线斜率为负数的时候,方案数我们不会计算到,由于每一个斜率为正数的直线旋转 \(90°\) 都可以得到一条斜率为负数的直线,所以 \(ans\) 直接乘上 \(2\),然后减去三点在同一行或同一列的方案数。
后者可以直接 \(O(nm)\) 组合数暴力求出,对于 \(ans\) 直接预处理 \(\gcd\) 的前缀和乱搞即可。
时间复杂度 \(O(nm)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1010;
ll n,m,ans,sum[N][N];
ll gcd(ll x,ll y)
{
if (!y) return x;
return gcd(y,x%y);
}
int main()
{
scanf("%lld%lld",&n,&m);
n++; m++;
for (ll i=0;i<=max(n,m);i++)
sum[i][0]=sum[0][i]=sum[0][i-1]+i;
for (ll i=1;i<=n;i++)
for (ll j=1;j<=m;j++)
{
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+1LL*gcd(i,j);
ans+=sum[i-1][j-1]-i*j+1LL;
}
ans*=2LL;
for (ll i=1;i<=n;i++)
for (ll j=1;j<=m;j++)
ans-=((i-2LL)*(i-1LL)/2LL+(j-2LL)*(j-1LL)/2LL);
ll cnt=n*m;
printf("%lld",cnt*(cnt-1)*(cnt-2)/6-ans);
return 0;
}