1. 程式人生 > >『POJ 3714』raid 題解 (平面最近點對)

『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