高級數據結構系列 —— poj 2352 題解(JS版本)

很久沒有刷題了,這兩天在複習線段樹,順便就記錄一下本題的解題過程。
點擊這裏看題數組

題目大意: 給你n個點表明n個星星,以該點爲起點向x,y軸做垂線,會獲得一個矩形,這個矩形裏面所包含的星星數(不包括它自己)稱爲該星星的等級 ,要求輸出在0~n-1等級各有多少個星星。注意給出的點的順序頗有講究,是 \color{red}{按照y座標升序給出,若是y座標相同,則按照x座標升序給出。}
因此其實咱們只需關注每個點在它以前出現的那些點是否在這個點與原點所圍成的矩形中就行,在這個矩形中有幾個點那麼該點的level就是幾。
好比說,我輸入[[1,1], [5,1], [7,1], [3,3], [5,5]],先看第一個點,[1,1]以前尚未點出現,因此很明顯[1,1]的level就是0,level[0] ++,對於第二個點,[5,1]與原點所圍成的矩形中出現了[1,1],因此[5,1]的level就是1,level[1] ++,第三個點[7,1]與原點圍成的矩形中有[1,1]和[5,1],那麼[7,1]的level就是2,level[2] ++;對於[3,3],它與原點所圍成的矩形中是[1,1], 因此[3,3]的level是1,level[1] ++;最後是[5,5],它與原點圍成的矩形中有[1,1],[5,1]和[3,3],因此[5,5]的level就是3,level[3] ++; 因此最後得出來的level數組爲[1,2,1,1,0].
到這裏,相信你已經看懂了題目,那麼該怎麼解呢?

暴力法

暴力你們應該都想得出來,兩重for循環,既然y座標是按照升序的,那麼我只要考慮在當前點以前的點的x座標是否不大於當前點的x座標便可,第一重for裏面表明是當前點,第二重for是當前點以前的點。bash

思考一下

若是用暴力的話,對於數據量比較小的時候還能hold得住,可是數據量一大呢,兩重for的時間複雜度但是O(n^2),若是我輸入的是一萬個數呢,那麼就要跑10000 * 10000次啊,要是再多點呢?顯然這是不行的。那麼咱們再思考一下,對於某個點(tx, ty),它的y座標咱們不用考慮了,對於x座標,咱們只要找出在它以前出現的點中,哪些點的x座標\color{red}{在[0,tx]之間}不就好了嗎,統計出來假設有n個點,那麼level[n]++ 便可。因此問題到這邊就轉換成了\color{red}{求區間和}的問題了。熟悉線段樹的同窗應該知道線段樹就是用來高效解決這類求區間和的問題的,還不瞭解線段樹的點擊這裏函數

線段樹解法

咱們用線段樹去維護star的x區間,每次讀入一個點,就去更新線段樹(這裏我把建樹和更新放在一塊兒操做了),更新完以後就去讀取區間和,代碼以下:ui

var maxn = 0;  //x座標的邊界
       var level = [];
       var tree = [];  //維護x座標區間
       function getStarLevel(stars) {
           maxn = 0;
           level = [];
           for(let i = 0; i < stars.length; i ++)
               level[i] = 0;
           tree = [];
           for(let i = 0; i < stars.length; i ++) {
               if(stars[i][0] > maxn)
                   maxn = stars[i][0];
           }
           for(let i = 0; i < 4 * maxn; i ++)  //線段樹的缺點,空間消耗大
               tree[i] = 0; //初始化線段樹
           for(let i = 0; i < stars.length; i ++) {
               build_tree(1, 0, maxn, stars[i][0]);  //在線建樹
               level[find(1, 0, maxn, 0, stars[i][0]) - 1] ++; //這邊 -1 是不能把自身給算上去
           }

           return level;
       }

       /*
       * 遞歸建樹
       * */
       function build_tree(id, left, right, x) {
           if(left === right) {
               tree[id] ++;
               return;
           }
           let mid = (left + right) >> 1;
           if(mid >= x)
               build_tree(id << 1, left, mid, x);
           else
               build_tree(id << 1 | 1, mid + 1, right, x);
           tree[id] = tree[id << 1] + tree[id << 1 | 1];
       }

       function find(id, minX, maxX, left, right) {
           if(minX === left && maxX === right)
               return tree[id];
           let mid = (minX + maxX) >> 1;
           if(right <= mid)
               return find(id << 1, minX, mid, left, right);
           else if(left > mid)
               return find(id << 1 | 1, mid + 1, maxX, left, right);
           else
               return find(id << 1, minX, mid, left, mid) + find(id << 1 | 1, mid + 1, maxX, mid + 1, right);
       }
複製代碼

這裏用個簡單的例子來說解一下這段代碼,[[1,1], [2,2], [3,1]],首先我是遍歷一遍點去拿到x的邊界值即maxn=3,也就是說,咱們須要維護的就是一個[0,3]的區間,該樹以下圖所示spa

注意每一個節點上面的數字是它的id,每一個節點框裏面的數字表明的是區間,也就是說tree[1]表明的就是區間[0,3]的和,每次的更新操做(這裏我直接把更新和建樹合併了,就是build_tree函數)其實本質上改變的是葉子節點的值,當讀入第一個點[1,1]時,它的x座標是1,咱們從樹根往下遞歸,發現tree[5]表明的就是x=1,因此tree[5] ++,此時tree[5]就是1,以後又會從葉子往上更新,找到tree[5]的父節點,即tree[2],也更新爲了1,再往上,tree[1]也更新爲1,更新完了以後查找在第一個點以前有沒有點的x座標是小於第一個點的x座標即1的,即查詢[0,1]區間的和,代碼仍是從樹根開始找,發現樹根表明的是[0,3]的區間和,二分後遞歸查找左子樹,發現左子樹表明的是[0,1],正好就是該區間,因此之間返回tree[2]節點的值便可,以後的更新查找操做就和第一次的同樣了,這裏就不贅述了。

小結

線段樹雖然能節省時間上的開支,可是也是創建在了開出不少數組的前提下,是一種以空間換時間的解決方案,其實涉及區間和,單點更新的題也能夠用樹狀數組來作,時間複雜度和線段樹同樣,可是空間上能省出不少來,有興趣的能夠本身去了解一下,本題也能夠用樹狀數組解決.net

相關文章
相關標籤/搜索