青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 97, 文章 - 22, 評論 - 81, 引用 - 0
數據加載中……

二維線段樹

      二維線段樹最主要用于平面統計問題。類似一維線段樹,最經典的就是求區間最值(或區間和),推廣到二維,求得就是矩形區域最值(或矩形區域和),對于矩形區域和,二維樹狀數組更加高效,而矩形區域最值,更加高效的方法是二維RMQ,但是二維RMQ不支持動態更新,所以二維線段樹還是有用武之地的。
      如果對一維線段樹已經駕輕就熟,那么直接來看下面兩段對比,就可以輕松理解二維線段樹了。
      一維線段樹是一棵二叉樹,樹上每個結點保存一個區間和一個域,非葉子結點一定有個兒子結點,兒子結點表示的兩個區間交集為空,并集為父結點表示的區間;葉子結點的表示區間長度為1,即單位長度;域則表示了需要求的數據,每個父結點的域可以通過兩個兒子結點得出。
      二維線段樹是一棵四叉樹,樹上每個結點保存一個矩形和一個域,非葉子結點一定有二或四 個兒子結點,兒子結點表示的四個矩形交集為空,并集為父結點表示的矩形;葉子結點表示的矩形長寬均為1,域則表示了需要求的數據,每個父結點的域可以通過四個兒子結點得出。
      一個4x3的矩形,可以用圖1的樹形結構來表示,給每個單位方塊標上不同的顏色易于理解。
圖1
       圖1中,每個葉子結點的單位面積為1,非葉子結點表示的矩形進行四分后,如圖2所示,四個子矩形分別表示的是兒子結點表示的矩形區域。特殊的,當矩形面積為1 X H或者W X 1的時候,變成了一維的情況,這就是為什么有些結點有四個子結點,而有些結點只有兩個子結點的原因了。
圖2
       基本結構已經清楚了,按照慣例,要介紹一下時空復雜度。
       首先來看空間復雜度,一個 W x H 的矩形,根結點表示的矩形是W x H,令N = max{W, H},那么這棵二維線段樹的深度D = log2(N)+1,當這棵樹是一棵滿四叉樹的時候,結點數達到最大值,根據等比數列求和公式,最大情況的結點數為 (4^D - 1) / 3。更加直觀的,當N = W = H = 2^k, 必定是一棵滿四叉樹,結點數為(4^D-1) / 3 = ( 4^(k+1) - 1 ) / 3 = (2^(2k+2)-1) / 3,去掉分子上的零頭1,約等于(4/3)*N^2, 所以空間復雜度為O(N^2)。
       再來看時間復雜度,需要分情況:
       建樹:建樹時必定訪問到每個結點,而且都是訪問一次,所以建樹的復雜度為O(N^2);
       單點更新:每次更新一個單位矩形的值,訪問時只會訪問從樹的根結點到葉子結點的一條路徑,所以單點更新的復雜度為O( log2(N) )。
       區域詢問:情況類似一維的區間詢問。從根結點開始拆分區域,當詢問區域完全覆蓋結點區域時,不需要遞歸往下走,總體復雜度是O( log2(N) * log2(N) )  ? 這里還是打個問號先,具體是一個log還是兩個log記不清了,找個時間證明一下,可以肯定的是,不會退化成O(N)。
     
       接下來看看每個樹結點需要保存一些什么信息, 以最值為例,除了保存最值以外,有可能需要知道這個最值在整個矩形的具體坐標,所以我們的最值信息dataInfo需要保存三個信息,posx和posy表示最值的具體位置,val保存最值,由于二維線段樹的空間復雜度為O(N^2),所以坐標信息不會很大,為了盡力節省內存,坐標值用short來存即可。最值val的話看實際情況而定,一般用int就夠了。
       treeNode則是線段樹結點的結構體,其中成員由dataInfo對應的最值和son[4]表示的子結點編號組成,我們的線段樹結點采用靜態結點,即每個線段樹結點都對應靜態數組 nodes中的某個元素,便于通過編號在O(1)的時間內獲取到對應樹結點的指針,son[4]記錄了四個子結點在nodes中的下標。仔細觀察可以發現,如果對于一棵線段樹,保證所有結點編號都連續的情況下,如果父結點的編號確定,那么子結點的編號也就確定了。例如,根結點編號為1,那么四個子結點編號為2、3、4、5,父結點編號為2,四個子結點的編號為6、7、8、9,根據數學歸納法,當結點編號為p,那么它的四個子結點編號為(4*p-2+x),其中x取值為[0, 3],所以四個子結點的編號信息可以通過O(1)的時間計算出來,就不用存儲在線段樹結點上了,大大節省了內存開銷。
結構定義代碼如下:

#define LOGN 10
#define MAXN (1<<LOGN)
#define MAXNODES 3*(1<<(2*LOGN)/+ 100)
#define son(x) (p*4-2+x)
// 最值信息
struct dataInfo {
    short posx, posy;
    int val;
    dataInfo() {
        posx = posy = val = -1;
    }
    dataInfo(short _posx, short _posy, int _val) {
        posx = _posx;
        posy = _posy;
        val = _val;
    }
};
// 線段樹結點信息
struct treeNode {
    // int son[4]
    dataInfo maxv, minv;
    void reset() {
        maxv = dataInfo(0, 0, INT_MIN);
        minv = dataInfo(0, 0, INT_MAX);
    }
}nodes[ MAXNODES ];

// 注意,這里需要返回指針,因為在后續使用中需要對這個結點的信息進行改變,如果返回對象的話只是一個copy,不會改變原結點的內容
treeNode* getNode(int id) {
    return &nodes[id];
}

       這時候,我們發現線段樹的結點上還缺少一個很重要的信息,因為每個結點表示了一個矩形區域,那為什么沒有存下這個矩形區域呢?毋庸置疑,也是為了節省內存,在接下來的區域查詢、單點更新的介紹中會講到,這個區域其實在每次遞歸的時候是作為傳參進入函數內部的,結點編號確定,矩形區域就確定了,所以沒必要存儲在結點中。
       為了處理方便,我們還需要封裝一個區間類(由于矩形可以表示成兩個不同維度的區間,所以這里只需要封裝一個區間類即可,矩形類的操作沒有區間內那么簡單,一目了然),它支持一些基本操作,如判交、判包含、取左右半區間等等、,具體代碼如下:

struct Interval {
    int l, r;
    Interval() {}
    Interval(int _l, int _r) {
        l = _l;
        r = _r;
    }
    // 區間中點 
    int mid() {
        return (+ r) >> 1;
    }
    // 區間長度 
    int len() {
        return r - l + 1;
    }
    // 左半區間 
    Interval left() {
        return Interval(l, mid());
    }
    // 右半區間 
    Interval right() {
        return Interval(mid()+1, r);
    }
    // 區間判交
    bool isIntersectWith( Interval& tarI ) {
        return !( l > tarI.|| r < tarI.);
    } 
    // 區間判包含
    bool isInclude( Interval& tarI ) {
        return l <= tarI.&& tarI.<= r;
    }
    bool in (int v) {
        return l <= v && v <= r;
    }
};
        那么接下來就是建樹了,建樹就是遞歸生成結點的過程,這里的生成并非創建,原因是因為我們的結點是靜態的。每建一次樹,只是把所有線段樹結點的信息進行一次重置,對于一個W x H的矩形,假定它的兩個對角線坐標為(1, 1) - (W, H),那么我們首先將它切割成四個矩形,令WM = (1+W)/2, HM = (1+H)/2對角線坐標分別為:
        (1, 1) - (WM, HM)
        (WM+1, 1) - (W, HM)
        (1, HM+1) - (WM, H)
        (WM+1, HM+1) - (W, H)
        如圖3所示,四個切割完后的矩形如下:
圖3
        這個切割過程是遞歸進行的,當某次切割的矩形為單位面積的時候,即為遞歸出口。當然還有一種情況,就是當某次切割后的矩形的某一維為1,而另一維大于1時,這里假設W = 1,H > 1,那么繼續切割時會發現WM+1 > W,導致 (WM+1, 1) - (W, HM) 和 (WM+1, HM+1) - (W, H) 這兩個矩形面積為負,所以在遞歸入口處需要判斷是否有某一維的右端點小于左端點,如果有,這種矩形是不合法的,不能做為線段樹的結點,不需要繼續往下遞歸創建。
        
建樹代碼如下:
void build_segtree(int p, Interval xI, Interval yI) {
    // 空矩形(右端點小于左端點)
    if(xI.len() <= 0 || yI.len() <= 0) {
        return ;
    }
    treeNode* now = getNode(p);
    // 結點初始化
    now->reset();
    // 單位矩形
    if(xI.len() == 1 && yI.len() == 1) {
        return ;
    }
    build_segtree( son(0), xI.left(), yI.left() );
    build_segtree( son(1), xI.right(), yI.left());
    build_segtree( son(2), xI.left(), yI.right() );
    build_segtree( son(3), xI.right(), yI.right());   
}
        其中p為當前線段樹結點的編號,son(0) - son(3)則是作為子結點編號穿參進入切割后的矩形getNode(p)用于獲取編號為p的結點的結構指針,建樹的目的就是為每個結點進行初始化,如果求的是最大值,那么將結點上的域都設成 INT_MIN ( 只要比所有接下來要插入的值小即可 ),如果求的是最小值,那么結點上的域都設成 INT_MAX( 只要比所有接下來要插入的值大即可 )。
        建樹完畢后,這些結點都有了一個初始值,那么接下來就是需要在矩形的每個點插入一個值,然后更新線段樹上每個結點的最值信息了。
        插入過程和建樹過程的思想是一致的,同樣是將矩形切割成四份,因為插入的是一個點,所以不可能同時存在于任意兩個矩形中(因為是個矩形是互不相交的),所以每次四分只會選擇一個矩形進行插入,為了讓代碼簡介,我們還是先將矩形進行切割,然后模擬所有的矩形都能夠插入,然后在遞歸入口處判斷該點是否在矩形區域中,如果不在矩形區域直接返回。這樣,當遞歸到單位矩形的時候,這個點的坐標一定是和矩形的坐標重合的,就可以直接更新該矩形所在的線段樹結點的域信息了,更新完這個單位矩形還不夠,還需要將信息傳遞給它的父結點,因為每次更新只有一個點,所以改變的結點只有從這個單位矩形所在結點到根結點的一條路徑上的結點,所以復雜度是樹的深度,即O(log2(N))。
插入結點(單點更新)代碼如下:
bool insert_segtree(int p, Interval xI, Interval yI, int x, int y, int val) {
    if(xI.len() <= 0 || yI.len() <= 0) {
        return false;
    }
    if( !xI.in(x) || !yI.in(y) ) {
        return true;
    }
    treeNode *now = getNode(p);
    if(xI.len() == 1 && yI.len() == 1) {
        now->maxv = now->minv = dataInfo(x, y, val);
        return true;
    }
    bool isvalid[4];
    isvalid[0] = insert_segtree( son(0), xI.left(), yI.left(), x, y, val );
    isvalid[1] = insert_segtree( son(1), xI.right(), yI.left(), x, y, val );
    isvalid[2] = insert_segtree( son(2), xI.left(), yI.right(), x, y, val );
    isvalid[3] = insert_segtree( son(3), xI.right(), yI.right(), x, y, val ); 
   
    // 通過四個子結點的信息更新父結點 
    now->maxv = dataInfo(0, 0, MIN_VAL);
    now->minv = dataInfo(0, 0, MAX_VAL);
    int i;
    for(= 0;< 4; i++) {
        if( !isvalid[i] ) continue;
        treeNode *sonNode = getNode(son(i));
        now->maxv = sonNode->maxv.val > now->maxv.val ? sonNode->maxv : now->maxv;
        now->minv = sonNode->minv.val < now->minv.val ? sonNode->minv : now->minv;
    }
    return true; 
}
       可以發現,插入的核心代碼和建樹是一致的,但是這里的插入操作,返回了一個值,表示的是當前插入的線段樹結點p是否合法,因為我們在插入的時候無論如何都會將矩形切割成四份,沒有去考慮上文中提到的有一維為1的情況,父結點的域值是通過子結點回溯上來進行更新的,如果子結點不合法,不應該作為更新的依據,所以作為父結點,需要知道哪些結點是不合法的。
       有了更新,當然需要詢問,沒有詢問,更新也就失去了意義。
       詢問一般是區域詢問(單點詢問就沒必要用線段樹了),如圖4所示,在一個4 x 3的矩形中,需要詢問灰色的矩形(3 x 2的矩形,以下統一稱為詢問矩形)中最大的數是什么。首先來說說原理,同樣,和建樹以及插入操作一樣,我們首先不斷將矩形進行切割,每當訪問到一個結點的時候將詢問矩形和結點矩形進行判交測試,一共有以下幾種情況:
       1、詢問矩形 和 結點矩形 沒有交集 (圖4中所有白色的葉子結點);
       2、詢問矩形 完全包含 結點矩形 (圖4中根結點的第三個子結點);
       3、詢問矩形 不完全包含 結點矩形,并且存在交集(圖4中根結點的第一、二、四個子結點);
       首先我們需要保存一個全局最大值信息,這個信息可以通過引用的方式傳遞到函數中去,在遞歸的過程中不斷迭代更新;
       對于第1、2兩種情況都是不需要繼續往下遞歸的,第1種情況不會影響目前的最大值,第2種情況需要將結點上的最大值和全局最大值進行比較,保留大的那個;第三種情況有交集,所以我們需要將矩形繼續分割,直到出現第1或者第2種情況為止,而且一定是可以出現的。
圖4
區域詢問代碼如下:
// query_type 0 最大值   1最小值
void query_segtree(int p, Interval xI, Interval yI, Interval tarXI, Interval tarYI, dataInfo& ans, int query_type) {
    if(xI.len() <= 0 || yI.len() <= 0) {
        return ;
    }
   
    if( !tarXI.isIntersectWith(xI) || !tarYI.isIntersectWith(yI) ) {
        return ;
    }
    treeNode *now = getNode(p);
   
    // 最大值優化 
    if(query_type == 0 && ans.val >= now->maxv.val) {
        return ;
    }
    // 最小值優化 
    if(query_type == 1 && ans.val <= now->minv.val) {
        return ;
    }
       
    if(tarXI.isInclude(xI) && tarYI.isInclude(yI)) {
        if(query_type == 0) {
            ans = now->maxv;
        }else {
            ans = now->minv;
        }
        return ;
    }
    query_segtree( son(0), xI.left(), yI.left(), tarXI, tarYI, ans, query_type );
    query_segtree( son(1), xI.right(), yI.left(), tarXI, tarYI, ans, query_type );
    query_segtree( son(2), xI.left(), yI.right(), tarXI, tarYI, ans, query_type );
    query_segtree( son(3), xI.right(), yI.right(), tarXI, tarYI, ans, query_type ); 
}    
        這里加入了一個query_type,表示求的是最大值還是最小值,因為有的時候既需要知道最大值,又需要知道最小值,為了簡化函數個數引入的一個變量。這里我們發現,當求最大值的時候,如果 詢問矩形 和 結點矩形 是有交集并且并非完全包含的情況下,如果結點最大值比全局最大值(以上代碼中的ans即全局最大值信息)還小,那么沒必要再往下遞歸了,因為遞歸下去的最大值不會比當前結點的最大值大,這個優化很重要。
        以上就是二維線段樹通過三個函數實現求區域最值的全部內容,建樹(build_segtree)、插入(insert_segtree) 詢問(query_segtree),其實當我們將這三個函數中的 yI 這個區間變成[1, 1]的時候,就變成了一維線段樹的模板了。

posted on 2015-10-06 18:56 英雄哪里出來 閱讀(30285) 評論(0)  編輯 收藏 引用 所屬分類: 算法專輯

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲精华国产欧美| 亚洲国产aⅴ天堂久久| 夜夜嗨av一区二区三区网站四季av| 久久亚洲国产成人| 久久精品1区| 一区二区在线观看视频| 老司机免费视频久久| 久久偷看各类wc女厕嘘嘘偷窃| 在线日韩中文| 亚洲国产精品久久久久秋霞不卡 | 亚洲免费播放| 亚洲另类自拍| 国产片一区二区| 噜噜噜91成人网| 欧美日韩999| 欧美亚洲日本网站| 久久视频这里只有精品| 亚洲精品一级| 亚洲在线成人精品| 亚洲国产经典视频| 日韩一本二本av| 国产一区二区丝袜高跟鞋图片| 久热爱精品视频线路一| 欧美啪啪成人vr| 久久久久99| 欧美日韩成人综合在线一区二区| 亚洲欧美在线播放| 美女性感视频久久久| 亚洲一区二区精品在线| 久久天天躁狠狠躁夜夜av| 一本不卡影院| 久久精品天堂| 亚洲尤物在线视频观看| 久久人人97超碰国产公开结果| 一本大道久久a久久精二百| 亚洲免费在线视频一区 二区| 亚洲东热激情| 欧美亚洲免费高清在线观看| 亚洲精品久久久久久久久久久久久| 亚洲一二三区精品| 日韩视频第一页| 久久久久久久97| 午夜精品一区二区三区四区 | 亚洲永久精品国产| 亚洲国产精品久久久久秋霞蜜臀| 制服丝袜亚洲播放| 99精品久久久| 母乳一区在线观看| 老司机一区二区| 国产日本亚洲高清| 国产精品99久久99久久久二8| 91久久国产综合久久蜜月精品 | 亚洲欧洲精品成人久久奇米网| 国产农村妇女精品一二区| 亚洲免费av片| 亚洲伦伦在线| 欧美大香线蕉线伊人久久国产精品| 久久精品视频在线免费观看| 国产精品久久国产精品99gif| 亚洲激情视频在线| 91久久久在线| 欧美大片在线观看| 欧美成人资源网| 亚洲国产二区| 美女脱光内衣内裤视频久久影院| 久久在线播放| 在线观看欧美黄色| 久久亚洲美女| 亚洲电影免费在线观看| 亚洲黄色在线观看| 欧美国产日韩一区二区在线观看 | 欧美高清一区二区| 亚洲国产精品一区二区尤物区| 久久久久欧美精品| 欧美成人久久| 亚洲精品视频在线播放| 欧美国产日本在线| 日韩午夜高潮| 香蕉av777xxx色综合一区| 国产精品第一页第二页第三页| 一区二区三区久久网| 午夜老司机精品| 韩国欧美一区| 蜜桃av一区二区三区| 亚洲精品国产精品国产自| 一本色道久久88精品综合| 欧美性猛交99久久久久99按摩| 一级日韩一区在线观看| 欧美在线观看一二区| 激情欧美一区二区三区在线观看 | 日韩视频二区| 久久成人人人人精品欧| 亚洲国产mv| 欧美视频免费看| 欧美在线视频在线播放完整版免费观看| 久久九九免费视频| 亚洲欧洲综合| 国产精品日韩专区| 毛片一区二区三区| 一区二区欧美精品| 老司机午夜免费精品视频| 一区二区国产日产| 国产视频一区二区三区在线观看| 久久欧美中文字幕| 亚洲私拍自拍| 欧美www视频| 欧美亚洲一区二区在线| 亚洲福利电影| 国产三级精品三级| 欧美精品高清视频| 久久精品国产成人| 亚洲天堂av电影| 欧美国产91| 久久精品亚洲精品国产欧美kt∨| 最新热久久免费视频| 国产视频一区三区| 欧美日韩一区二区在线视频| 久久久噜噜噜久久人人看| 在线亚洲精品| 亚洲国产日韩欧美在线99| 久久露脸国产精品| 亚洲欧美久久久久一区二区三区| 亚洲经典一区| 黄色免费成人| 国产欧美日韩亚洲精品| 欧美天天视频| 欧美全黄视频| 欧美 日韩 国产 一区| 久久精品99久久香蕉国产色戒| 夜夜嗨av色综合久久久综合网| 亚洲电影激情视频网站| 久久久久久亚洲精品杨幂换脸| 亚洲欧美日韩一区二区在线| 亚洲精品久久久蜜桃| 欲色影视综合吧| 国内久久视频| 国产亚洲欧美日韩精品| 国产欧美日韩综合一区在线观看| 欧美日韩在线播放一区二区| 欧美日韩成人在线观看| 欧美国产精品va在线观看| 欧美va天堂| 免费成人小视频| 免费一级欧美在线大片| 久热精品在线视频| 麻豆av一区二区三区久久| 久久久夜夜夜| 免费观看成人www动漫视频| 蜜臀va亚洲va欧美va天堂| 久久久久久久综合色一本| 久久久久国产一区二区三区四区| 先锋影院在线亚洲| 久久久国产亚洲精品| 久久成人免费网| 久久尤物视频| 欧美高清视频一区二区三区在线观看 | 欧美暴力喷水在线| 欧美成人福利视频| 亚洲国产精品久久| 亚洲黄色在线看| 日韩视频在线观看| 亚洲一级二级| 久久精品欧美日韩精品| 免费美女久久99| 欧美日韩国产123区| 国产精品普通话对白| 国产曰批免费观看久久久| 亚洲福利视频二区| 一区二区三区视频在线看| 亚洲欧美日韩另类| 噜噜噜躁狠狠躁狠狠精品视频| 欧美黑人国产人伦爽爽爽| 99精品欧美一区二区三区| 亚洲欧美制服中文字幕| 久热精品视频在线观看| 欧美日韩国产大片| 国内精品福利| 在线视频日韩| 久久久精品国产免费观看同学 | 性娇小13――14欧美| 久久久亚洲高清| 亚洲免费观看高清完整版在线观看熊| 亚洲一级在线| 免费在线播放第一区高清av| 国产精品久久久久久户外露出| 伊人成人网在线看| 亚洲亚洲精品在线观看| 麻豆精品传媒视频| 亚洲午夜免费福利视频| 免费观看一级特黄欧美大片| 国产精品免费区二区三区观看| 伊人久久亚洲美女图片| 亚洲一区二区三区视频| 亚洲第一在线综合网站| 亚洲女同在线| 欧美日韩亚洲一区| 亚洲精美视频| 久久精品亚洲一区二区三区浴池 | 欧美一二三区在线观看| 亚洲欧洲综合另类|