这是题目:
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,最后得到的计数值除以4(正方形的4条边都会贡献计数)就是正方形的个数。
- 时间复杂度:枚举两点的复杂度为O(n^2);查找,采用hash table(此题我采用链地址法解决冲突),时间复杂度近似认为O(1),因此总的时间复杂度为O(n^2)。对于n<=1000的数据规模可以接受。
- 如何计算另外两点的坐标?请看下图。
- 最后,也是常常被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;
}