題目地址: 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 19977struct 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的次序將點排序。
這樣遍曆點的時候,可以保證兩組測試都是不會重複的。