题目地址: http://poj.org/problem?id=2002
题目的大意是在二维平面上有很多点,问有多少种可能组成正方形。假设这些点的坐标都可以用int来表示
例如下面图是一个例子:
可以形成的正方形是6个,有一个比较特殊的红色的正方形。
这个题目如果用暴力所有的话,显然是4层循环,看选中的四个点能否构成正方形。不过显然这么高的复杂度,一定会超时的。
下面的做法是我在discussion里看到的,如果自己设计还真的设计不出来,本文加上了一些自己的理解和想法。
我们可以这样想,对于任意两个点(P1 P2), 假设是一个向量,方向是P1P2,我们可以求出这个向量一侧的两个点P3P4,P1P2P3P4正好构成正方形。
例如图所示
在这个图中,利用三角形相等的条件,我们可以知道对于P2P4,它在x上的变化正好P1P2在y上的变化,P2P4在y上的变化正好等于P1P2在X上的变化。对于P3也可以得出类似的结论。
所以
P[3].x=P[1].x+(P[1].y-P[2].y); P[3].y=P[1].y+(P[2].x-P[1].x); P[4].x=P[2].x+(P[1].y-P[2].y); P[4].y=P[2].y+(P[2].x-P[1].x);下一步就是哈希的魅力所在了,求出这两个点,然后再哈希里检索,如果在哈希表里,则将ans + 1。否则跳过。
不过我们会发现一个问题,对于同一个正方形P1P2P4P3, 我们在判断的时候,查找到P1P2, 总结果数ans+1, 对于P2P4, ans+1, 对于P4P3 ans +1, 对于P3P1 ans+1。
所以这样最后的结果要 /4。这样程序的总效率从O(n^4)降低到O(n^2)
代码如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> using namespace std; #define MAX_NUM 20000 #define PRIME 19977 struct Point { int x; int y; }; bool cmp(const Point& a,const Point &b) { if(a.x==b.x) return a.y<b.y; return a.x<b.x; } int HashTable[PRIME]; Point data[1005]; int nPoint; int GetHashCode(const Point& p) { return (abs(p.x) * 50 + abs(p.y)) % PRIME; //这里要保证hashcode是正数,我在这里RE好几次 } void InsertIntoHash(int i)//插入哈希表 { int hashCode = GetHashCode(data[i]); if(HashTable[hashCode] == -1) { HashTable[hashCode] = i; } else { int t = (hashCode + 1) % PRIME; while(HashTable[t] != -1) { t++; t %= PRIME; } HashTable[t] = i; } } bool PointEqual(const Point &p1,const Point &p2) { return p1.x == p2.x && p1.y == p2.y; } bool SearchInhashTable(const Point &point)//在哈希表里查找 { int hashCode = GetHashCode(point); if(HashTable[hashCode] == -1) { return false; } else { int t = hashCode; while(HashTable[t] != -1 && !PointEqual(point, data[HashTable[t]]) ) { t++; t %= PRIME; } if(HashTable[t] == -1) { return false; } return true; } } int main() { int x,y; while(scanf("%d", &nPoint) && nPoint) { memset(HashTable, -1, sizeof(HashTable)); for( int i = 0; i < nPoint; i++) { scanf("%d%d", &data[i].x, &data[i].y); } //sort(data, data + nPoint, cmp);//下面改进后修改的代码 for(int i = 0; i < nPoint; i++) { InsertIntoHash(i); } int sum = 0; Point tmp; int ans = 0; for( int i = 0; i < nPoint; i++) { for( int j = 0; j < nPoint; j++)//改进后修改为 j = i + 1 { if( i != j) { x=data[i].x+data[i].y-data[j].y; y=data[i].y+data[j].x-data[i].x; tmp.x = x; tmp.y = y; if(!SearchInhashTable(tmp)) continue; x=data[j].x+data[i].y-data[j].y; y=data[j].y+data[j].x-data[i].x; tmp.x = x; tmp.y = y; if(SearchInhashTable(tmp)) ans++; } } } printf("%d\n", ans/4); } return 0; }当然算法其实还是有加速的余地,至少可以提高将近2倍的效率。因为我们发现在我们的测试数据中P1P2会测试,P2P1也会测试,并且这样的测试是没有意义的(因为P4P3会做等效的测试)
所以我们可以把内侧的循环,改成 j = i+1
不过这里还有一个问题,如果原始序列中点的排序是 P1 P2 P4 P3, 那样的话,我们在测试P1P2后,等效测试是P4P3, 这样就没有测试过P3P4这种本应做测试的情况。
那么如何改进呢,答案是将所有的点排序,按照x优先,然后y的次序将点排序。
这样遍历点的时候,可以保证两组测试都是不会重复的。