BZOJ 3505 数三角形 (数论 组合数 gcd)

来源:互联网 发布:windows 2008ntp服务器 编辑:程序博客网 时间:2024/05/16 12:23

BZOJ 3505 数三角形 (数论 组合数)

Description
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。下图为4x4的网格上的一个三角形。

注意三角形的三点不能共线。

Input
输入一行,包含两个空格分隔的正整数m和n。

Output

输出一个正整数,为所求三角形数量。

Sample Input
2 2

Sample Output
76

数据范围

1<=m,n<=1000

思路:
计算三角形个数可以计算取出三个点的方案数再减去会三点共线的方案数。
n * m的网格上有(n+1) * (m+1)个整点,那么取三个点的方案数就是C((n+1) * (m+1), 3)。
再算三点共线的方案数
考虑在(a,b) (x,y)两点构成的线段上有gcd(a-x,b-y)-1个整点(a>x,b>y),我们固定(a,b) (x,y)为共线的三点中左边两个,那么第三个点的方案数就是gcd(a-x,b-y)-1,但是可以优化,把这线段平移到原点处,那么会发现其实只要枚举(0,0) (x-a,y-b),其他的线段都可以通过平移得到。
一种(0,0) (i,j)的线段可以平移出(n-i+1)*(m-j+1)种不同方案。
那么为什么可以用gcd这样算呢?因为(i/gcd,j/gcd)是在直线上距离(0,0)最近的点,设(u,v)=(i/gcd,j/gcd),cc为1~gcd-1闭区间里的整数,于是发现(u * cc,v * cc)就是我们要求的点。

#include<cstdio>  #define LL long long  int gcd[1010][1010];  int n, m;  LL ans;int G(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] = G(b, a % b);  }//预处理gcdvoid init(){      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++) G(i, j);  }int main(){      scanf("%d%d", &n, &m);      init();    LL 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 ){//两点(i,j)与(0,0)不重合                 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);    return 0;  }