数三角形
题解
xx(省略)数学题!!!考场上怎么打错了!!!
组合+数论+容斥
显而易见,三角形的个数=所有三个点的组合-水平线的三点组合-铅垂线的三点组合-斜线的三点组合
我们先把n与m分别加1,求出横线数与竖线数。
所有三个点的组合:
水平线的三点组合:
铅垂线的三点组合:
重点是下面的,十分重要。
子命题:与两点间的整点个数为
接下来是证明,因为是整点,所以他们两点构成的大三角形一定可以被分解为若干个小三角形(最少一个),且其边为整数。
最小三角形两边应为与
所以除两端点外的整点个数为
为了没有重复的,我们可以用两端点枚举中间的点。
这样时间复杂度为。这样还是要超时。
因为矩阵的对称性,我们只需枚举的情况
我们可以考虑将左下的那个移动到原点,先看这种情况的数量,再枚举其可能的平移操作。
设这个点的的坐标为,则这个点可以向上平移,向右平移,所以总共可以平移的状态为种。
每种的点数为,所以,
斜线的三点组合:
别忘了,最后的三角形数还应除即6,因为有重复的三角形。
源码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
using namespace std;
typedef long long LL;
const LL INF=0x7f7f7f7f7f7f;
LL n,m,ans;
#define gc() getchar()
template<typename _T>
inline void read(_T &x)
{
_T f=1;x=0;char s=gc();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=gc();}
while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=gc();}
x*=f;
}
LL gcd(LL a,LL b)
{
if(!b)
return a;
return gcd(b,a%b);
}
int main()
{
read(n);read(m);
LL tmp=(n+1LL)*(m+1LL);
ans=tmp*(tmp-1LL)*(tmp-2LL)/6LL;
ans-=(n+1LL)*m*(m-1LL)*(m+1LL)/6LL;
ans-=(m+1LL)*n*(n-1LL)*(n+1LL)/6LL;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
{
LL tmp=(gcd(i+1,j+1)-1)*(n-i)*(m-j)*2LL;
ans-=tmp;
}
printf("%lld",ans);
return 0;
}
谢谢!!!