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();
		}
		

	}
}

整体的代码是把逻辑写复杂了,后面有简化版

涉及到的知识点:

  1. 向哈希容器中填充自定义的类的对象时要注意,一定要重写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 + ")";
      		}
      
  2. 整体的思路是,

    1. 每次向set中添加一个点

    2. 将该点和其他所有已添加的点进行结合形成一条边,同时考虑这个边是下边、上边或者对角线的情况,这样我们就需要六个点来判断,分别对应三种情况的剩余两个点

      求这六个点的坐标可以采用极坐标的方式来求比较简单,也即将一个已知点通过坐标轴变化放到原点,形成一个极坐标,然后剩下的点其实就是当前连线的旋转,注意对角线情况求边的时候长度要除以根号2,并且最后求出来的点的坐标要用坐标轴变化换回去

    3. 去重:对于已添加的点,考虑过的就不能再考虑,因为如果三个点一样的话,最后形成的正方形一定是同一个,这就是visited集合的作用

    4. 因为每次都是考虑最新加进来的点,也就是说至少有一个点是新的,所以不需要在外层去重,也即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;
	}
}
posted @ 2020-06-27 22:08  别再闹了  阅读(451)  评论(0)    收藏  举报