世界真的很大
在数论中,组合数和欧拉函数一样是个神奇的东西
如果说凡是看到gcd就要想到欧拉的话
那凡是看到“选几个”就应该往组合数方面想了
这种代码简单的玩意儿
难度全在于如何应用,理解的不够好,或者建模方法有问题
都是可能能卡很久的,尤其是代码量真的很短的情况下
就更悲催了
比如我就已经看了一整个晚上都没有想出来
最后还得大神指导才想到了正确的建模方法
哎。。还是不行啊。。233
还是先看题
description
给定一个nxm的网格,请计算三点都在格点上的三角形共有多少个。下图为4x4的网格上的一个三角形。
注意三角形的三点不能共线。
input
输入一行,包含两个空格分隔的正整数m和n。
output
输出一个正整数,为所求三角形数量。
首先应该想到的是组合数
即求在m*n个点里面选3个点的组合数
但是这样是有毒的
所选的3个点有可能在一条直线上,而这种情况是不能组成三角形的
我们需要排除这样的情况
首先是三个点在同一列(行)的情况,直接算出行列,3的组合数再乘以有多少行和有多少列就好
还有就是三个点在同一斜边的情况,这个的处理就比较复杂了
考虑n^2枚举这样的斜边,再算出每条斜边3的组合数累加,整个矩阵的对角线特判一下
这样是不行的
首先会算漏。因为我们枚举的是边都在大矩形的边上的矩形的斜边,边都是整点
但有些斜边落在大矩形的边上的点并不是整点
于是考虑平移这样的斜边
这样理论上来讲是可以的,但是要考虑边长是gcd(边长)的几倍。方程化出来和下面的方法的方程是一样的
换一个角度考虑,不要枚举斜边了,直接考虑枚举在一条直线上的3个点吧,这样就不用考虑重复枚举的bug了,因为枚举的3个点肯定是不一样的。
那就考虑枚举3个点的两个端点,再用gcd算出两个端点之间有多少个整点,就有在端点确定情况下的方案数
但是呢,因为有平移的缘故,我们干脆就确定一个端点为(0,0),只枚举另一个端点的位置,整张图内能平移得到的个数就是(n-i)*(m-j)
完整代码:
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long dnt;
dnt n,m;
dnt com(dnt x)
{
if(x<3) return 0;
return x*(x-1)*(x-2)/6;
}
dnt gcd(dnt a,dnt b)
{
return b==0 ? a : gcd(b,a%b) ;
}
dnt sov()
{
dnt rt=0;
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
rt+=(gcd(i,j)-1)*(n-i)*(m-j);
return (rt*2);
}
int main()
{
scanf("%lld%lld",&n,&m);
n++,m++;
printf("%lld",com(m*n)-n*com(m)-m*com(n)-sov());
return 0;
}
嗯,就是这样