Description
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。下图为4x4的网格上的一个三角形。
注意三角形的三点不能共线。
Input
输入一行,包含两个空格分隔的正整数m和n。
Output
输出一个正整数,为所求三角形数量。
Sample Input
2 2
Sample Output
76
数据范围
1<=m,n<=1000
数据范围
1<=m,n<=1000
一开始不会做……orz了各路神犇之后才有了思路
首先答案就是所有取出三个点的方案数减去会三点共线的方案数
显然n*m的网格上有(n+1)*(m+1)个整点,然后令t=(n+1)*(m+1),那么取三个点的方案数就是t*(t-1)*(t-2)/6(就是排列组合啦T T)
然后要考虑怎么算三点共线的方案数
然后有一个结论是在(a,b) (x,y)两点构成的线段上有gcd(a-x,b-y)-1个整点(a>x,b>y)
我们固定(a,b) (x,y)为共线的三点中左边两个,那么第三个点的方案数就是gcd(a-x,b-y)-1
但是这样枚举abxy的复杂度是O(n^2*m^2)
优化是把这线段平移到原点处,那么会发现其实只要枚举(0,0) (x-a+1,y-b+1),其他的线段平移就可以了(这里强烈建议自己动手画一画!)
画完很容易发现这样(0,0) (x-a+1,y-b+1)的线段可以平移出(n-i+1)*(m-j+1)种不同方案,在 (x-a+1,y-b+1)的时候如果不在坐标轴上还要算两次
另外,先打表算gcd竟然能这么快……
#include<cstdio> #define LL long long int gcd[1010][1010]; int n,m; LL t,ans; inline int getgcd(int a,int b) { if (gcd[a][b])return gcd[a][b]; if (!a)return gcd[a][b]=b; if (!b)return gcd[a][b]=a; return gcd[a][b]=getgcd(b,a%b); } inline void calc() { for(int i=1;i<=m;i++)gcd[0][i]=i; for(int i=1;i<=n;i++)gcd[i][0]=i; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) getgcd(i,j); } int main() { scanf("%d%d",&n,&m); calc(); t=(n+1)*(m+1); ans=t*(t-1)*(t-2)/6; for (int i=0;i<=n;i++) for (int j=0;j<=m;j++) if (i||j) { if (!i||!j)ans-=(LL)(gcd[i][j]-1)*(n-i+1)*(m-j+1); else ans-=(LL)2*(gcd[i][j]-1)*(n-i+1)*(m-j+1); } printf("%lld",ans); }