[洛谷P3166]数三角形

15 篇文章 0 订阅

题目

传送门 to luogu

错误 の の 考场思路

对于其中一个点是原点 ( 0 , 0 ) (0,0) (0,0) 的情况:

任意两个共线的点 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) 都有:

x 1 ( x 1 , y 1 ) = x 2 ( x 2 , y 2 ) ∧ y 1 ( x 1 , y 1 ) = y 2 ( x 2 , y 2 ) \frac{x_1}{(x_1,y_1)}=\frac{x_2}{(x_2,y_2)}\wedge \frac{y_1}{(x_1,y_1)}=\frac{y_2}{(x_2,y_2)} (x1,y1)x1=(x2,y2)x2(x1,y1)y1=(x2,y2)y2

然后我还想到了皮克定理

百度百科:皮克定理是指一个计算点阵中顶点在格点上的多边形面积公式,该公式可以表示为 S = a + b 2 − 1 S=a+\frac{b}{2}-1 S=a+2b1其中 a a a 表示多边形内部的点数, b b b 表示多边形边界上的点数, S S S 表示多边形的面积。

正确の题解思路

膜拜大佬 o r z    o r z    o r z \tt{orz\; orz\; orz} orzorzorz

首先,用一个矩形将一个三角形“包住”。也就是说,找到一个最小的矩形,使得三角形完全在矩形内。

在这里插入图片描述

图中灰色的是三角形,外面的就是包住它的矩形。因为一个三角形只有一个“包围矩阵”,所以 不同的包围矩阵一定对应不同的三角形。而且 包围矩阵一定是原矩阵的子矩阵

子矩阵也有 O ( n 2 m 2 ) \mathcal O(n^2m^2) O(n2m2) 个啊,怎么搞?发现 长的一样的包围矩阵对应相同数量的三角形。所以只需要枚举包围矩阵的长和宽即可。个数是很好计算的。至少比这道题本身简单多了。枚举包围矩阵的复杂度降为 O ( n m ) \mathcal O(nm) O(nm)

因为包围矩阵是最小的可以包围三角形的矩形,所以 包围矩形的每条边上都有一个三角形的顶点。一个三角形只有三个顶点,所以说三角形的某个顶点一定抵住了两条边,也就是说,三角形至少有一个顶点在包围矩阵的顶点上

我们先假设包围矩阵的长和宽(指 格子 的数量,也就是说,有 n + 1 n+1 n+1 个格点)为 n n n m m m 。接下来可以分类讨论:

情况一:三角形只有一个顶点在矩形顶点上

已经在矩形顶点上的点有四种情况(东南、东北、西南、西北),剩下两个点一个在长边上、一个在宽边上(并且不在顶点上),乘法原理 4 ( n − 1 ) ( m − 1 ) = 4 n m − 4 m − 4 n + 4 4(n-1)(m-1)=4nm-4m-4n+4 4(n1)(m1)=4nm4m4n+4

情况二:三角形有两个顶点在矩形顶点上

类似的,我可以先固定一个点,假设它是左下角。接下来又得分类讨论了:

0x01 右下角
在这里插入图片描述
如图,第三个点一定在最顶上的边。共 n − 1 n-1 n1 个。

0x02 左上角

右下角 情况相似(只需要将上图顺时针旋转 9 0 o 90^\text{o} 90o 即可),共 m − 1 m-1 m1 个。

0x03 右上角
在这里插入图片描述
上图的方案明显是合法的,看出此时第三个点可以在 ( n + 1 ) ( m + 1 ) − 4 (n+1)(m+1)-4 (n+1)(m+1)4(减 4 4 4 是去掉矩形的顶点)的格点中随便取——除了 三点共线 的情况。线段上的格点怎么算?

将左下角视为坐标系原点 ( 0 , 0 ) (0,0) (0,0) ,则右上角(线段的另一个端点)为 ( n , m ) (n,m) (n,m) ,结论:线段上除端点外有 gcd ⁡ ( n , m ) − 1 \gcd(n,m)-1 gcd(n,m)1 个点。这里简单说明一下。

在线段上的格点 ( a , b ) (a,b) (a,b) 一定满足 b a = m n \frac{b}{a}=\frac{m}{n} ab=nm(斜率相同嘛,不考虑 a = 0 a=0 a=0 的情况)。若要 a m n \frac{am}{n} nam 为整数, a a a 必须含有 n gcd ⁡ ( n , m ) \frac{n}{\gcd(n,m)} gcd(n,m)n 的因子。而 0 < a < n 0<a<n 0<a<n ,故只有 gcd ⁡ ( n , m ) − 1 \gcd(n,m)-1 gcd(n,m)1 个取值。

总结一下,一共 ( n + 1 ) ( m + 1 ) − gcd ⁡ ( n , m ) + 1 (n+1)(m+1)-\gcd(n,m)+1 (n+1)(m+1)gcd(n,m)+1 个。

0x04 总的来说
总的来说,方案数是 ( n − 1 ) + ( m − 1 ) − 4 + ( n + 1 ) ( m + 1 ) − gcd ⁡ ( n , m ) + 1 = n m + 2 n + 2 m − gcd ⁡ ( n , m ) − 4 (n-1)+(m-1)-4+(n+1)(m+1)-\gcd(n,m)+1=nm+2n+2m-\gcd(n,m)-4 (n1)+(m1)4+(n+1)(m+1)gcd(n,m)+1=nm+2n+2mgcd(n,m)4

发现上面的所有情况都可以上下翻折(但是不可以左右翻折),所以带的是系数 2 2 2

情况二的总和,就是 2 n m + 4 n + 4 m − 2 gcd ⁡ ( n , m ) − 8 2nm+4n+4m-2\gcd(n,m)-8 2nm+4n+4m2gcd(n,m)8

情况三:三角形有三个顶点在矩形顶点上

显然,只有 4 4 4 种,枚举哪个矩形顶点不重合。

综上!!!

情况一: 4 n m − 4 n − 4 m + 4 4nm-4n-4m+4 4nm4n4m+4
情况二: 2 n m + 4 n + 4 m − 2 gcd ⁡ ( n , m ) − 8 2nm+4n+4m-2\gcd(n,m)-8 2nm+4n+4m2gcd(n,m)8
情况三: 4 4 4
总和: 6 n m − 2 gcd ⁡ ( n , m ) 6nm-2\gcd(n,m) 6nm2gcd(n,m)

搞定了!

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}
inline int getGcd(int a,int b){
	while(b) a %= b, swap(a,b);
	return a;
}

struct Point{
	int x, y;
	Point(int X=0,int Y=0):x(X),y(Y){}
	long long operator*(const Point &that)const{
		return x*that.y-y*that.x;
	}
};

int n, m;
/** @brief 用来骗分的暴力代码,这里就一并给出了 */
void work1(){ // 暴力大法好!!! 
	long long ans = 0;
	for(int x1=0; x1<=n; ++x1)
	for(int y1=0; y1<=m; ++y1)
	for(int x2=0; x2<=n; ++x2)
	for(int y2=0; y2<=m; ++y2)
	for(int x3=0; x3<=n; ++x3)
	for(int y3=0; y3<=m; ++y3)
		if(Point(x2-x1,y2-y1)*Point(x3-x1,y3-y1) != 0)
			++ ans;
	writeint(ans/6);
	putchar('\n');
}

int main(){
	// freopen("triangle.in","r",stdin);
	// freopen("triangle.out","w",stdout);
	n = readint(), m = readint();
	if(n <= 10 and m <= 10){
		work1();
		return 0;
	}
	long long ans = 0;
	for(int i=1; i<=n; ++i) // 矩形的个数是(n-i+1)(m-j+1) 
		for(int j=1; j<=m; ++j)
			ans += 1ll*(n-i+1)*(m-j+1)*(6*i*j-2*getGcd(i,j));
	writeint(ans);
	putchar('\n');
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值