poj2002 Squares

这是题目:

2:正方形
查看 提交 统计 提问
总时间限制: 3500ms 内存限制: 65536kB
描述
给定直角坐标系中的若干整点,请寻找可以由这些点组成的正方形,并统计它们的个数。
输入
包括多组数据,每组数据的第一行是整点的个数n(1<=n<=1000),其后n行每行由两个整数组成,表示一个点的x、y坐标。输入保证一组数据中不会出现相同的点,且坐标的绝对值小于等于20000(此处翻译有误,应为:点到原点的距离小于等于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


====================================================================================

  1. 解题思路:枚举点集中的任意两点,假设它们在正方形的一条边上,通过几何关系计算另外两个点的坐标,查找这两个点是否都在点集中,若是,则计数+1,最后得到的计数值除以4(正方形的4条边都会贡献计数)就是正方形的个数。
  2. 时间复杂度:枚举两点的复杂度为O(n^2)查找,采用hash table(此题我采用链地址法解决冲突),时间复杂度近似认为O(1),因此总的时间复杂度为O(n^2)。对于n<=1000的数据规模可以接受。
  3. 如何计算另外两点的坐标?请看下图。
  4. 最后,也是常常被oj忽略的一点:内存回收,具体而言是链地址法中链表结点的回收。从尾结点向首结点回收,用指针栈保存沿途结点的地址。

====================================================================================

代码清单:
//hash table
//采用的散列函数是key=(x^2+y^2)%prime。选择一个prime number尽可能减少冲突。
//基本思路是,点集中任取两个点,假设它们在正方形上,算出另外两个点的坐标,查找这另外两个点是否都在点集中。
//取两个点的复杂度为O(n^2);查找由于使用了hash,复杂度近似认为O(1);因此总的时间复杂度为O(n^2)。对于n<=1000的数据规模可以接受。
//有两点需要注意:1.算出另外两点的坐标,用直观的坐标运算即可。2.每一个正方形有四条边,遍历这四条边的时候各增加计数1次,所以最后的“正方形个数”要除以4才是实际的个数。
#include <cstdio>
#include <stack>
using namespace std;

#define MAXP 1005
#define PRIME 21911 //随便选的一个质数。
#define OUTOFRANGE 999999

typedef struct _point
{
	int x;
	int y;
} point;

typedef struct _hashtab
{
	int x;
	int y;
	struct _hashtab *next;
} hashtab;

//点的个数最多是1000个
point p[MAXP];
//散列长度。根据散列函数和选定的prime,散列长度,即key值的可能性有prime种:0~prime-1。
hashtab ht[PRIME];

void InitHashTab()
{
	for (int i=0; i<PRIME; ++i)
	{
		ht[i].x=OUTOFRANGE;
		ht[i].y=OUTOFRANGE;	
		//没有这两句x和y的初始化答案是wrong answer!为何?
		//当然!下一组数据搜点时,可能会与上一组已有的点重合,所以必须初始化。那么应该初始化为多少呢?
		//由题意,点的域是以原点为圆心、以20,000为半径的圆,包括边。所以选一个大于20,000的数字即可。
		ht[i].next=NULL;
	}
}

void BuildPointAndHashTab(int n)
{
	int x, y;
	int key;
	for (int i=0; i<n; ++i)
	{
		scanf("%d%d", &x, &y);
		p[i].x=x;
		p[i].y=y;

		key=(x*x+y*y)%PRIME;
		//用链地址法解决冲突
		hashtab *pt=&(ht[key]);
		while (pt->next != NULL)	pt=pt->next;
		//当前位置的pt->next==NULL,在当前位置插入数据
		pt->x=x;
		pt->y=y;
		//插入数据后它的next不再可以为NULL,要为这个next分配空间
		pt->next = new hashtab;
		//注意把新分配的空间中的next指针设置为NULL
		pt->next->next=NULL;
	}
}

bool IsExist(int x, int y)
{
	int key=(x*x+y*y)%PRIME;
	hashtab *pt=&(ht[key]);

	while (1)
	{
		if ((pt->x==x) && (pt->y==y))
			return true;
		else
		{
			if (pt->next != NULL) pt=pt->next;
			else return false;
		}
	}
}

//回收链表的空间,否则会造成内存泄漏。当然,在oj上影响不大。
void DestroyLinkList()
{
	hashtab *pt;
	stack<hashtab *> recycle;
	for (int i=0; i<PRIME; ++i)
	{
		pt=&(ht[i]);
		//找到链的末尾结点,从后向前回收。可是没有prev指针怎么办呢?用栈啦!
		while (pt->next != NULL)	//说明还不是链上的最后一个结点
		{
			recycle.push(pt);
			pt=pt->next;
		}

		//接下来把栈里面的指针指向的空间都回收就OK了
		while (!recycle.empty())
		{
			pt=recycle.top();
			delete (pt->next);
			recycle.pop();
		}
	}
}

int main()
{
	freopen("D:\\in.txt", "r", stdin);
	freopen("D:\\out.txt", "w", stdout);

	int n, cnt;

	while (1)
	{
		scanf("%d", &n);
		if(n==0) break;

		cnt=0;
		InitHashTab();

		BuildPointAndHashTab(n);

		//开始扫描点对
		for (int i=0; i<n; ++i)
		{
			for (int j=i+1; j<n; ++j)
			{
				int x1=p[i].x;	int y1=p[i].y;
				int x2=p[j].x;	int y2=p[j].y;
				int dx=x1-x2;	int dy=y1-y2;

				int x31=x1+dy;	int y31=y1-dx;
				int x41=x2+dy;	int y41=y2-dx;

				if(IsExist(x31, y31) && IsExist(x41, y41))	++cnt;

				int x32=x1-dy;	int y32=y1+dx;
				int x42=x2-dy;	int y42=y2+dx;

				if(IsExist(x32, y32) && IsExist(x42, y42))	++cnt;
			}
		}

		printf("%d\n", cnt/4);

		DestroyLinkList();
	}

	return 0;
}


你可能感兴趣的:(hash,检索,哈希表,Squares,poj2002)