1807:正方形
1807:正方形
这道题比较重要,可以带出许多知识点
-
总时间限制:
8000ms
-
单个测试点时间限制:
4000ms
-
内存限制:
65536kB
-
描述
给出平面上一些点的坐标,统计由这些点可以组成多少个正方形。注意:正方形的边不一定平行于坐标轴。
-
输入
输入包括多组测试数据。每组的第一行是一个整数n (1 <= n <= 1000),表示平面上点的数目,接下来n行,每行包括两个整数,分别给出一个点在平面上的x坐标和y坐标。输入保证:平面上点的位置是两两不同的,而且坐标的绝对值都不大于20000。最后一组输入数据中n = 0,这组数据表示输入的结束,不用进行处理。
-
输出
对每组输入数据,输出一行,表示这些点能够组成的正方形的数目。
-
样例输入
4 1 0 0 1 1 1 0 0 9 0 0 1 0 2 0 0 2 1 2 2 2 0 1 1 1 2 1 4 -2 5 3 7 0 0 5 2 0 -
样例输出
1 6 1
先上我写的最终代码
package com.jiading.noi;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Scanner;
/*
* 1807:正方形
* http://noi.openjudge.cn/ch0305/1807/
*/
public class Problem21 {
static private class Point {
Integer x, y;
Point(Integer x, Integer y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
try {
Point objPoint=(Point)obj;
return this.x.equals(objPoint.x)&&this.y.equals(objPoint.y);
}catch(ClassCastException e) {
return false;
}
}
@Override
public int hashCode() {
/*int hash = 7;
hash = 71 * hash + x;
hash = 71 * hash + y;
if(hash<0) {
hash=hash*-1;
}
return hash;*/
int result=x;
result=31*result+y;
return result;
}
@Override
public String toString() {
return "("+x+","+y+")";
}
}
static Long retangles = (long) 0;
static HashSet<Point> pointSet = new HashSet<>();
static HashSet<Point> visited = new HashSet<>();
private static void findRetangles(int x1, int y1) {
// 对pointSet中的每个点都遍历一遍,找6个相关点
Iterator<Point> iterator = pointSet.iterator();
while (iterator.hasNext()) {
Point next = iterator.next();
int x2 = next.x;
int y2 = next.y;
// 左上的两个关键点
Point left1 = new Point(y1 - y2 + x1, x2 - x1 + y1);
Point left2 = new Point(y1 - y2 + x2, x2 - x1 + y2);
// 右下的两个关键点
Point right1 = new Point(y2 - y1 + x1, x1 - x2 + y1);
Point right2 = new Point(y2 - y1 + x2, x1 - x2 + y2);
//对角只有对角点都是正的时候才符合要求
// 对角的两个关键点
if((x1+x2+y2-y1)%2==0 && (y1+y2+x1-x2)%2==0 && (x1+x2+y1-y2)%2==0 && (y1+y2+x2-x1)%2==0) {
Point cross1 = new Point((x1+x2+y2-y1)/2, (y1+y2+x1-x2)/2);
Point cross2 = new Point((x1+x2+y1-y2)/2, (y1+y2+x2-x1)/2);
/*System.out.println(pointSet.contains(cross1));
System.out.println(pointSet.contains(cross2));
System.out.println(visited.contains(cross1));
System.out.println(visited.contains(cross2));*/
if (pointSet.contains(cross1) && pointSet.contains(cross2) && !visited.contains(cross1)
&& !visited.contains(cross2)) {
retangles++;
//System.out.println("cross1:"+cross1+"cross2:"+cross2+"this:("+x1+","+y1+")next:"+next);
}
}
if (pointSet.contains(left1) && pointSet.contains(left2) && !visited.contains(left1)
&& !visited.contains(left2)) {
retangles++;
//System.out.println("left1:"+left1+"left2:"+left2+"this:("+x1+","+y1+")next:"+next);
}
if (pointSet.contains(right1) && pointSet.contains(right2) && !visited.contains(right1)
&& !visited.contains(right2)) {
retangles++;
//System.out.println("right1:"+right1+"right2:"+right2+"this:("+x1+","+y1+")next:"+next);
}
visited.add(next);
}
visited.clear();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int pointNums = scanner.nextInt();
while (pointNums != 0) {
//System.out.println("-----------------------------------");
pointSet.clear();
for (int i = 0; i < pointNums; i++) {
int x = scanner.nextInt();
int y = scanner.nextInt();
findRetangles(x, y);
pointSet.add(new Point(x, y));
}
System.out.println(retangles);
retangles=(long) 0;
pointNums = scanner.nextInt();
}
}
}
整体的代码是把逻辑写复杂了,后面有简化版
涉及到的知识点:
-
向哈希容器中填充自定义的类的对象时要注意,一定要重写hashcode和equals方法!
-
hashcode:哈希容器将以此为哈希值选择元素填充位置。选择一个完美的hash function是很难的,但是我们也不需要这样,因为哈希冲突是被允许的,不会对结果造成影响,只是哈希冲突越少的话性能越好而已。对于图模型中常见的“点”类型,hash function的设计是被广泛讨论过的,可以看这里:https://stackoverflow.com/questions/9135759/java-hashcode-for-a-point-class 。有两个方法可以直接用,有论证它们的哈希冲突会比较少,但是我们就不论证了:
@Override public int hashCode() { int hash = 7; hash = 71 * hash + this.x; hash = 71 * hash + this.y; return hash; }@Override public int hashCode() { int result = x; result = 31 * result + y; return result; }第一个的话可以看到对x的放大倍数比较大,相对容易爆int,所以可以选择第二种方法。
-
如果说hash function的设计好坏不太重要,那么equals方法的设计就至关重要了。因为它直接决定了能不能在哈希容器中找到你要找的元素,也就哈希容器能否正常工作。对于点类,我们往往希望通过其坐标来判断点是不是相等,但是默认的判断方式是根据其对象的地址,所以需要重新equals方法,我们的目标是设计一个完备的equals方法,也即是和各种类型的对象都能正确比较的方法:
@Override public boolean equals(Object obj) { if(obj==this) { return true; } if(obj==null) { return false; } if (this.getClass() != obj.getClass()) { return false; } Point objPoint = (Point) obj; return this.x.equals(objPoint.x) && this.y.equals(objPoint.y); }- if(obj==this):这个其实是非必须的,但是作为一种特殊情况,把它放在第一句进行判断可以在有些时候避免无意义的运算,毕竟如果是一个对象的话坐标当然是相等的
- if(obj==null):这一句是必须的,因为null不属于任何类,它就是一个空指针,没有getClass方法,所以下面的通用判断方法不适用
- if (this.getClass() != obj.getClass()):如果这两个对象都不是一个类的,当然就不一样了
- 最后就是,既然判断都是点类的了,那么就可以把obj强转为点类,从而获取其坐标进行判断了,当然如果把不是点类的对象强转为点类会报错
我们一般只重写equals方法,而保留==运算符作为判断对象地址,从而判断是不是同一个对象的方法
有一个坑,就是整数常量池。我一开始大意了,最后一句写的是
return this.x==objPoint.x && this.y==objPoint.y;,测试的时候用小的值也没有问题,但是其实在大的值上是不对的,因为Java的整数有一个常量池,-128到127之间的整数都是用的同一个对象,所以是没问题的,但是超出这个范围就是新建的对象了,判断就是false了!这点一定要注意!java的常量池是一个坑点! -
另外,我也建议同时重写一下toString方法,这样print的时候会很方便,有些IDE也能在调试时直接显示toString方法的结果,很方便调试
@Override public String toString() { return "(" + x + "," + y + ")"; }
-
-
整体的思路是,
-
每次向set中添加一个点
-
将该点和其他所有已添加的点进行结合形成一条边,同时考虑这个边是下边、上边或者对角线的情况,这样我们就需要六个点来判断,分别对应三种情况的剩余两个点
求这六个点的坐标可以采用极坐标的方式来求比较简单,也即将一个已知点通过坐标轴变化放到原点,形成一个极坐标,然后剩下的点其实就是当前连线的旋转,注意对角线情况求边的时候长度要除以根号2,并且最后求出来的点的坐标要用坐标轴变化换回去
-
去重:对于已添加的点,考虑过的就不能再考虑,因为如果三个点一样的话,最后形成的正方形一定是同一个,这就是visited集合的作用
-
因为每次都是考虑最新加进来的点,也就是说至少有一个点是新的,所以不需要在外层去重,也即visited每次选点都清除一次
-
方法的复杂度是O(n^2)的
但是我的思路其实是绕弯路了,有一个更简单的思路,就是只考虑对角线情况,选定一个点,将其和其他所有点的连线作为对角线分别考虑,求出对角线上剩下两个点看其是否在点的集合中。去重时,我们可以将循环设计为:
for (i=0; i<n-1; i++)
{
for (j=i+1; j<n; j++)
也就是说,我们只考虑点和在其后面的点之间的连线(该点之前的,对应的正方形已经被前面的点找出来了),相对于将对角线看为有向的,就避免了同一条对角线上的重复,但是因为一个正方形有两条对角线,所以最后求出的结果要除以2。代码见下:
import java.util.HashSet;
import java.util.Scanner;
import java.util.Vector;
public class Main {
public static Vector<node> vec = new Vector<node>();
public static HashSet<node> hm = new HashSet<node>();
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int i,j,n,ans;
Scanner sc = new Scanner(System.in);
int xx,yy,x1,x2,x3,x4,y1,y2,y3,y4;
while (true)
{
n=sc.nextInt();
if (n==0)
{
break;
}
vec.clear();
hm.clear();
for (i=0; i<n; i++)
{
xx = sc.nextInt();
yy = sc.nextInt();
node nd = new node(xx,yy);
vec.add(nd);
hm.add(nd);
}
ans = 0;
for (i=0; i<n-1; i++)
{
for (j=i+1; j<n; j++)
{
x1 = vec.get(i).x;
x2 = vec.get(j).x;
y1 = vec.get(i).y;
y2 = vec.get(j).y;
if ((x1+x2+y2-y1)%2==0 && (y1+y2+x1-x2)%2==0 && (x1+x2+y1-y2)%2==0 && (y1+y2+x2-x1)%2==0)
{
x3 = (x1+x2+y2-y1)/2;
y3 = (y1+y2+x1-x2)/2;
node nd1 = new node(x3,y3);
x4 = (x1+x2+y1-y2)/2;
y4 = (y1+y2+x2-x1)/2;
node nd2 = new node(x4,y4);
if (hm.contains(nd1) && hm.contains(nd2))
{
ans++;
}
}
}
}
ans /= 2;
System.out.println(ans);
}
}
}
class node {
public Integer x,y;
public node(int xx, int yy)
{
x = xx;
y = yy;
}
/**
* 为了使用HashSet, 需要重写hashCode和equals方法
*/
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if(getClass() != obj.getClass())
return false;
node other = (node)obj;
if(!x.equals(other.x))
return false;
if(!y.equals(other.y))
return false;
return true;
}
public int hashCode()
{
int res = 1;
res = 31*res + x;
res = 31*res + y;
return res;
}
}

浙公网安备 33010602011771号