『POJ 3714』raid 題解 (平面最近點對)
原題連結(戳我)
思路:
第一次看到這道題時,相信不少人都會想到暴力列舉,但是一看資料範圍: N = 100000
woc這題能做?
當然不能做也就不大可能放到OJ上,於是乎我就開始在草稿紙上畫了幾個點:
Several minutes later...
woc這道題能做?(摔)
當然這是不可能的,畢竟作為一個蒟蒻,心態不好點恐怕早就afo了呀
當然,最後我還是沒能靠自己想出來,於是乎在老師的慫恿之下,我默默地打開了百度,搜尋了平面最近點對
其實一開始,我是拒絕的,因為。。。看不懂
然鵝最後還是大概看懂了
大概是這麼個意思:
運用分治的思想
先想辦法把問題變小:
比如說用二分,找到中間點:
一刀下去,DUANG:
這個可憐的點集就變成兩半了
然後繼續切切切,到出現孤零零的一個點時:
很明顯這個可憐的點木有女票,所以這個分治對它造成了極大值點傷害的打擊(沒有點配對就不能得到距離)
於是乎我們就得到了第一個遞迴邊界:
if(ll==rr) return BG;
如果切到最後,剩下了兩個點的話:
很明顯這兩個點就不再是single了
於是我們就返回它們之間的親密值(距離),這就是第二個邊界:
if(rr-ll==1) return get_dis(nd[ll],nd[rr]);
然後回到上一層,我們就能得到最優解。。。等等,有什麼不對勁?
接下來就是重頭戲了:
從圖中我們很明顯可以發現,一刀切下去後,兩邊分別求出解,再從它們之中取最小值,這麼做很明顯不對
因為左邊點和右邊的偏左邊那個點很明顯要近很多
於是我們就想到了在左邊和右邊的點之中兩個點兩個點地列舉
但這太慢了,基本和直接暴力列舉沒有什麼區別
於是經過幾分鐘的思考(看題解)
我們想到了一個優化:
如果求出當前的最優解為橙線,那麼很明顯綠色區域以外的點都可以不去嘗試了
但這樣的話程式碼實現明顯有點困難
於是乎我們就想到了先按照x軸排序,先把距離差超過綠線的忽略掉,把綠線以內的存入一個臨時陣列內:
然後再把臨時數組裡的點按y座標座標排序,列舉點時,每當列舉到綠線以外時,我們都break掉,也就相當於忽略了綠線以外的點:
就這樣,這一堆需要列舉的點就被我們減少到了3個
然後時間複雜度就噌噌噌地往下掉
至於這道題,我們通過題意可以知道,只有種類不同的點才用計算距離,於是乎我們在結構體裡除了座標外多加一個bool變數,代表點的種類:
struct node { double ii,jj; bool the_kind;//<--- }
然後每次進入求兩點距離的get_dis函式的時候,如果兩點種類相同,我們就直接返回極大值,這樣的話如果一路走來都是同一種點,這份程式碼中的ans就會一直保持極大值,就會一直列舉左右兩邊組合的所有情況,這樣就能保住程式碼的正確性
另外,聽說還有更優秀的方法:就是按y座標排序的時候利用上一次的排序結果,借用merge()來降低排序時間複雜度,這種我看得不是很懂,大家就自己問一問萬能的度娘吧。
完整程式碼:
1 #include <cstdio> 2 #include <algorithm> 3 #include <cmath> 4 #define rg register 5 #define llint long long 6 #define usi unsigned 7 using namespace std; 8 const int N=1008611; 9 const double BG = 100861111111111.0; 10 11 struct node 12 { 13 double ii,jj; //該點的縱座標ii,橫座標jj 14 bool the_kind; //該點的種類,0表示待攻擊點,1代表特工 15 }nd[200002],tmp[200002]; 16 int t,n; //t組資料,n個待攻擊點和n個agent 17 18 inline bool cmpx(node a,node b) //比較橫座標 19 { 20 return a.jj < b.jj; 21 } 22 inline bool cmpy(node a,node b) //比較縱座標 23 { 24 return a.ii < b.ii; 25 } 26 inline double get_dis(node a,node b)//獲得兩點間距離 27 { 28 if(a.the_kind==b.the_kind) return BG;//兩點種類相同,不計算距離 29 return sqrt((a.ii-b.ii)*(a.ii-b.ii)+(a.jj-b.jj)*(a.jj-b.jj)); 30 } 31 inline double divide_it(int ll,int rr) //分治求出當前子問題的解 32 { 33 if(ll==rr) return BG; //只有一個點,沒有點來配對 34 if(rr-ll==1) return get_dis(nd[ll],nd[rr]);//只有兩個點,將這兩個點配對 35 int mid = (ll+rr)>>1;int cnt = 0;//求出中間點下標,並把可用點(可能更新當前最優答案的點)數置為零 36 double ans = min(divide_it(ll,mid),divide_it(mid+1,rr));//求出子問題的最優解 37 for(rg int i=ll;i<=rr;++i) //開始掃描,找到可用點(關於x座標) 38 if(fabs(nd[i].jj-nd[mid].jj)<=ans) 39 ++cnt,tmp[cnt] = nd[i]; //把可用點存入臨時陣列內 40 sort(tmp+1,tmp+cnt+1,cmpy); //把可用點按照y軸座標排個序 41 for(rg int i=1;i<cnt;++i) 42 for(rg int j=i+1;j<=cnt&&tmp[j].ii-tmp[i].ii<=ans;++j)//列舉可用點 43 { 44 double qwq = get_dis(tmp[i],tmp[j]);//兩個點可能更新答案,計算距離 45 if(ans>qwq) ans = qwq; //更新 46 } 47 return ans; //返回最終結果 48 } 49 50 int main() 51 { 52 scanf("%d",&t); //t組資料 53 while(t--) 54 { 55 scanf("%d",&n); 56 for(rg int i=1;i<=n;++i) //輸入待攻擊點座標 57 scanf("%lf%lf",&nd[i].jj,&nd[i].ii),nd[i].the_kind = 0; 58 int loopvar = n<<1; 59 for(rg int i=n+1;i<=loopvar;++i) 60 scanf("%lf%lf",&nd[i].jj,&nd[i].ii),nd[i].the_kind = 1; 61 sort(nd+1,nd+1+loopvar,cmpx);//按照x座標排序 62 printf("%0.3f\n",divide_it(1,loopvar));//計算並輸出結果 63 } 64 return 0; 65 }
這道題就這麼愉快的地被我們切掉了
PS:卡了我3個小時的說QWQ,老是莫名其妙TLE
完成時間:2018/12/15 20:47