试题 C :直线
类型: 结果填空 ,总分:10分
【问题描述】
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。给定平面上2 × 3个整点{(x, y)|0 ≤ x < 2, 0 ≤ y < 3, x ∈ Z, y ∈ Z}
,即横坐标是0到 1 (包含 0 和 1) 之间的整数、纵坐标是 0 到 2 (包含0和 2) 之间的整数的点。这些点一共确定了 11 条不同的直线。
给定平面上 20× 21 个整点{(x, y)|0 ≤ x < 20, 0 ≤ y < 21, x ∈ Z, y ∈ Z}
,即横坐标是 0到 19 (包含 0 和 19) 之间的整数、纵坐标是 0 到 20(包含 0 和 20) 之间的整数的点。请问这些点一共确定了多少条不同的直线。
【结果】
40257
【思路一】
- 难点:存数据,直线表示,去重
- 自定义一个class存储 ( x , y ) (x,y) (x,y)二元组,用一个数组或者列表存下所有的点,遍历列表中每个点的组合
- 直线点斜式公式 y = k x + b y=kx+b y=kx+b,k和b能唯一确定一条直线,
- 假设直线过 P 1 = ( x 1 , y 1 ) P1=(x_1,y_1) P1=(x1,y1), P 2 = ( x 2 , y 2 ) P2=(x_2,y_2) P2=(x2,y2), k = y 1 − y 2 x 1 − x 2 k=\frac{y_1-y_2}{x_1-x_2} k=x1−x2y1−y2, b = y 1 − k x 1 b=y_1-kx_1 b=y1−kx1
- 但是注意:k的结果可能是小数,可能出现精度损失(此题中没有损失,但是在比赛的时候数据量大了这种东西不好验证,这里就不写用double存小数k和b的解法了),可能会出现除0的问题,
- 为了解决4中的问题,我们引入直线的两点式方程 y − y 2 y 1 − y 2 = x − x 2 x 1 − x 2 \frac{y-y_2}{y_1-y_2}=\frac{x-x_2}{x_1-x_2} y1−y2y−y2=x1−x2x−x2,经过交叉相乘和并化简,可以得到直线的标准式方程 ( y 1 − y 2 ) x + ( x 2 − x 1 ) y + ( x 1 y 2 − x 2 y 1 ) = 0 (y_1-y_2)x+(x_2-x_1)y+(x_1y_2-x_2y_1)=0 (y1−y2)x+(x2−x1)y+(x1y2−x2y1)=0,即 A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0的形式,点P1和P2是整数点,那么A、B、C三个算出来也都是整数,靠这三个点就能唯一确定一条直线了(注意要除以最大公因数,因为这种形式下系数扩大缩小相同的倍数是同一条直线)
- 在自定义一个类,存 ( A , B , C ) (A,B,C) (A,B,C)三元组,
- 用Set去重,但是注意,Java中对自定义类去重需要重写
equals()
和hashCode()
方法,可以参考。。。,这对于算法题来说显然有点复杂化了,所以可以直接去看下面的【思路二】
【代码一】
//自定义的数据结构
class point {
int x, y;
public point(int x, int y) {
this.x = x;
this.y = y;
}
}
class line {
int a, b, c;
public line(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj instanceof line) {
line l = (line)obj;
if(this.a == l.a && this.b == l.b && this.c == l.c) {
return true;
} else {
return false;
}
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(a, b, c);
}
}
//主类
public class Main {
private final static int X = 20;
private final static int Y = 21;
//递归辗转相除法求最大公因数
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
//主方法
public static void main(String[] args) {
Set<line> ans = new HashSet<>();
//把每个点存起来
List<point> list = new ArrayList<>();
for(int i = 0; i < X; i++) {
for (int j = 0; j < Y; j++) {
list.add(new point(i, j));
}
}
//遍历每个点的组合
for (int i = 0; i < list.size(); i++) {
for(int j = i + 1; j < list.size(); j++) {
int x1 = list.get(i).x, y1 = list.get(i).y;
int x2 = list.get(j).x, y2 = list.get(j).y;
int a = y1 - y2, b = x2 - x1;
int c = x1 * y2 - x2 * y1;
int d = gcd(gcd(a, b), c);
ans.add(new line(a / d, b / d, c / d));
}
}
System.out.println(ans.size());
}
}
【思路二】
- 对于思路一中存储和去重写法的复杂性,我经过思考和查找大佬们的解法,学习到可以用字符拼接的方式存储二维点的信息,String类是重写过
equals()
和hashCode()
方法的,可以用Set<String>
结构对结果去重 - 其他思路和【思路一】一致,细节写在注释里了
【代码二】
public class Main {
private final static int X = 20;
private final static int Y = 21;
//递归辗转相除法求最大公因数
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
//主方法
public static void main(String[] args) {
Set<String> ans = new HashSet<>();
List<Integer> list = new ArrayList<>();
//因为 x,y的取值确定了,可以用下面的方法
//如果x,y取值不确定,可以用字符串存成str=x_y的形式
//取的时候用split("_")分割x,y和Integer.parseInt()把String转成int
for(int i = 0; i < X; i++) {
for (int j = 0; j < Y; j++) {
//用一个int数存(x,y),x存在前两位,y存在后两位
list.add(i * 100 + j);
}
}
//遍历每个点的组合
for (int i = 0; i < list.size(); i++) {
//两点连一条直线,取第一个点
int p1 = list.get(i);
//一个int数转成(x,y)
int x1 = p1 / 100, y1 = p1 % 100;
for(int j = i + 1; j < list.size(); j++) {
//取第二个点
int p2 = list.get(j);
int x2 = p2 / 100, y2 = p2 % 100;
//计算标准式系数
int a = y1 - y2, b = x2 - x1;
int c = x1 * y2 - x2 * y1;
//约分
int d = gcd(gcd(a, b), c);
a /= d;
b /= d;
c /= d;
//set去重
ans.add(a + ":" + b + ":" + c);
}
}
System.out.println(ans.size());
}
}
水平有限不保证对,欢迎各位大佬评论交流