考拉学长杂题选讲最(水?)的一道题。
题面
题目描述
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。下图为4x4的网格上的一个三角形。注意三角形的三点不能共线。
样例输入
2 2
样例输出
76
题解
直接求不现实。所以一点点容斥?
我们先求出来在全部的$t$个点$((n+1)*(m+1))$中任意选三个点的全部组合
$frac{(t-2)*(t-1)*t}{3!}$ (解释:从t个数里选出第一个,剩下t-1个数中选第二个,t-2个数里选第三个,除以三个数的内部排列数)
然后我们尝试减去不合法的选择:
<1>.单行、单列上不合法的数量:与之前一样,只不过要在单行或者单列上选择。在单行上$frac{(m+1)*m*(m-1)}{3!}$,推广到每一行,乘以行数,即为:$frac{(m+1)*m*(m-1)}{3!}*(n+1)$,同理,在单列上为$frac{(n+1)*n*(n-1)}{3!}$,推广到每一列,乘以列数,即为:$frac{(n+1)*n*(n-1)}{3!}*(m+1)$。
<2>.斜着共线的三点的情况数量:设1点的坐标为$(x_1,y_1)$、$(x_2,y_2)$,则$gcd(x_1-x_2,y_1-y_2)-1$恰为点阵中1点和2点之间点的数量。一个非常显然的性质是:我们求出了一个斜线完全可以在点阵上平移,对称翻折。于是我们考虑优化:把其中一个点固定在左上角,然后枚举图中点的坐标,最后得出,左上角与枚举的端点相连所得的直线平移的可能总数为:$(n-x+1)*(m-y+1)$。于是得出公式:$(n-i+1)*(m-j+1)*2*(gcd(i,j)-1)$,两层for循环枚举i,j作为节点横纵坐标即可。
代码:
#include<iostream> #include<cstdio> #define rint register int using namespace std; long long n,m,t; long long ans=0,row,line,inc; inline int gcd(long long a,long long b) { return b==0?a:gcd(b,a%b); } int main() { scanf("%lld %lld",&m,&n); t=(m+1)*(n+1); ans=t*(t-1)*(t-2)/6; row=(n+1)*(m+1)*m*(m-1)/6;line=(m+1)*(n+1)*n*(n-1)/6; for(rint i=1;i<=n;++i) for(rint j=1;j<=m;++j) inc+=(n-i+1)*(m-j+1)*2*(gcd(i,j)-1); ans-=row;ans-=line;ans-=inc; cout<<ans<<endl; return 0; }