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

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);  
}//预处理gcd

void 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;  
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值