HDU1007 Quoit Design(換個思維)

與此題相同的還有ZOJ2107 本題通道:HDU1007

題意

給出若干個點(不大于100000個),求出這些點中距離最小的兩個點的距離的一半(最近點對問題)

解析

暴力解法是此類問題的一種解法,不過在這里肯定就超時了。正確的解法應該是分治算法,網上應該有很多解析。這里附上一些題解,還有關于最近點對問題的分析,下面是我對于此題AC的代碼

AC代碼

#include <iostream>
#include <cmath>
#include <algorithm>
#include <iomanip>
#define MAX 2147483647
using namespace std;
struct Point{
    double x,y;
}q[100010];
int dp[100010],n;
bool cmp(Point a,Point b){    //最開始的排序,x和y軸都從小到大
    if(a.x != b.x) return a.x < b.x;
    return a.y < b.y;
}
double dis(int a,int b){   //計算兩點之間的距離
    return sqrt((q[a].x-q[b].x)*(q[a].x-q[b].x) + (q[a].y-q[b].y)*(q[a].y-q[b].y));
}
bool cmp2(int a,int b){   //一個重要的排序
    return q[a].y < q[b].y;
}
double getClosePair(int left,int right){
    if(left == right) return MAX;   //這個區域只有一個點
    if(left + 1 == right) return dis(left,right);   //如果只有兩個點,直接返回距離
    int mid = (left + right)/2;
    double t1 = getClosePair(left, mid);   //遞歸求距離
    double t2 = getClosePair(mid + 1, right);
    double t = t1 < t2 ? t1 : t2;
    int temp = 0;
    for(int i = left; i <= right; i++)   //篩選x軸距離小于t的
        if(fabs(q[mid].x - q[i].x) <= t)
            dp[temp++] = i;
    sort(dp, dp + temp, cmp2);   //一個重要的排序
    for(int i = 0; i < temp; i++)
        for(int j = i+1; j < temp && q[dp[j]].y - q[dp[i]].y < t; j++){  //篩選y軸距離小于t的
            double t3 = dis(dp[i],dp[j]);   
            if(t3 < t) t = t3;   //更新最小值
        }
    return t;
}
int main(){
    while(cin>>n,n){
        for(int i=0; i<n; i++)
            cin>>q[i].x>>q[i].y;
        sort(q,q+n,cmp);
        cout<<fixed<<setprecision(2)<<getClosePair(0, n-1)/2<<endl;
    }
}

換個思維

  • 代碼我相信網上多的是,我這份也不過是參考最原始的改編的而已。但是我現在有一個問題。上面的getClosePair函數能不能改成下面這個樣子
double getClosePair(int left,int right){
    ......
    for(int i = left; i <= right; i++)   
        if(fabs(q[mid].x - q[i].x) <= t)
            dp[temp++] = i;
    //sort(dp, dp + temp, cmp2);   注意這個排序不要了 
    for(int i = 0; i < temp; i++) 
        for(int j = i+1; j < temp && fabs(q[dp[j]].y - q[dp[i]].y) < t; j++){  //注意這里的判斷加上了fabs
            double t3 = dis(dp[i],dp[j]);   
            if(t3 < t) t = t3;  
        }
    return t;
}
  • 為什么想要這樣改?上面的改動是這樣的,去掉了對dp數組的排序。用fabs來控制q[dp[j]].y - q[dp[i]].y的符號。我們現在返回去看為什么要有sort(dp, dp + temp, cmp2);,這個排序,是將這塊區域中的所有點按y值從小到大排序。因為j=i+1,所以j>i,所以q[dp[j]].y - q[dp[i]].y肯定是正數。難道說這個排序只是為了達到可以保持正數的效果?
  • 抱著這個只是為了保證正數的想法,我去掉了sort,加上了fabs,然后光榮WA了。

為什么?

每個程序都有它的核心代碼,理解了核心代碼就可以說理解了整個程序。而這個sort我覺得就是里面最妙的一筆。我用兩段代碼來解釋為什么fabs不行

代碼一:

  for(int i = 0; i < temp; i++) 
        for(int j = i+1; j < temp && fabs(q[dp[j]].y - q[dp[i]].y) < t; j++){  
            double t3 = dis(dp[i],dp[j]);   
            if(t3 < t) t = t3;  
        }

代碼二:

  for(int i = 0; i < temp; i++) 
        for(int j = i+1; j < temp; j++){  
          if(fabs(q[dp[j]].y - q[dp[i]].y) < t){
            double t3 = dis(dp[i],dp[j]);   
            if(t3 < t) t = t3;  
          }
        }

這兩段代碼的區別就在于這個fabs的判斷是放在for里面還是if里面。首先我告訴你們,代碼二的換到上面的AC代碼中,它的結果是對的,但是時間超了;代碼一換到上面的AC代碼中,它的結果是不對的。

區別我相信很多人能看出來,在for中判斷,如果不滿足條件,直接退出內循環,外循環的i++。如果是在for里面的if判斷,則不滿足條件后還會繼續外循環。也就是是否退出外循環的區別。

真相只有一個

真相只有一個.jpg

在用分治法求最小距離時,我們把區域一塊一塊的分開,當把兩塊合并的時候,最小的距離是 左邊那塊最小的距離右邊那塊最小的距離中間部分的最小距離這三段中取最小。

左邊和右邊直接可以得到。在計算中間部分時我們首先求得左邊那塊和右邊那塊較小的距離t,然后在合并后的區域中,找到離合并中點的x軸距離小于t的所有的點。

 for(int i = left; i <= right; i++)   //篩選x軸距離小于t的
        if(fabs(q[mid].x - q[i].x) <= t)   //mid表示中心位置
            dp[temp++] = i;

從上述得到的點中,再找出所有y軸x相對距離小于t的兩點,然后這兩點的距離,判斷是否還小于t。換句話說,就是如果兩個點的x軸相對距離和y軸相對距離都小于目前的最小距離t。則計算這兩點的實際距離,判斷是否小于t,如果小于則刷新t。這個過程中省去了大量的距離大的點的計算。代碼如下

sort(dp, dp + temp, cmp2);   //一個重要的排序
    for(int i = 0; i < temp; i++)
        for(int j = i+1; j < temp && q[dp[j]].y - q[dp[i]].y < t; j++){  //篩選y軸距離小于t的
            double t3 = dis(dp[i],dp[j]);   
            if(t3 < t) t = t3;   //更新最小值
        }

剛才說,把fabs放在for里面的if里面也可以,但是會超時。而這就是這個sort的第二個用處。

超時主要是在內層循環中,而能及時跳出內層循環,達到剪支效果的,毫無疑問就是根據y軸排序,這樣當某一點不再滿足小于時,后續的點肯定也不滿足。

現在想想好像是很簡單……用一個 sort搞定了剪支而已。但是,我的看法就是,這個分治,最重要的地方就在于合并的最小距離處理(就像剛才,如果用fabs就是超時)。而理解了這個sort,其實也就理解了整個程序(個人看法)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,769評論 0 33
  • 動態規劃(Dynamic Programming) 本文包括: 動態規劃定義 狀態轉移方程 動態規劃算法步驟 最長...
    廖少少閱讀 3,327評論 0 18
  • 個人學習批處理的初衷來源于實際工作;在某個迭代版本有個BS(安卓手游模擬器)大需求,從而在測試過程中就重復涉及到...
    Luckykailiu閱讀 4,779評論 0 11
  • sì 支zhī茶chá 對duì 酒jiǔ,賦fù 對duì 詩shī,燕yàn子zi 對duì 鶯yīng 兒é...
    每個人的孟母堂閱讀 1,264評論 0 6
  • 城市的喧囂,總讓我想要逃離......心里總覺得好累。 是我選錯了路,還是只是心懶?次次夢醒,總有淚水,在這繁華世...
    欽愛的Qinai閱讀 179評論 0 0