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

隨筆 - 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 英雄哪里出來 閱讀(29880) 評論(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>
            欧美影院精品一区| 亚洲欧洲日夜超级视频| 亚洲男人影院| 国产精品爽爽爽| 小嫩嫩精品导航| 亚洲欧美激情一区| 激情文学综合丁香| 欧美国产精品日韩| 欧美精品在线免费播放| 亚洲制服av| 欧美一区二区三区啪啪| 亚洲第一在线| 一本色道久久综合亚洲精品不卡 | 欧美三日本三级少妇三99| 亚洲综合三区| 久久精品久久99精品久久| 亚洲日本激情| 亚洲小说区图片区| 伊人夜夜躁av伊人久久| 亚洲黄页视频免费观看| 国产九九精品视频| 亚洲高清免费视频| 国产美女诱惑一区二区| 欧美国产日韩视频| 国产美女在线精品免费观看| 欧美激情亚洲国产| 国产美女高潮久久白浆| 亚洲电影专区| 国产精品外国| 亚洲人成在线观看一区二区| 国产欧美日韩在线观看| 亚洲国产精品传媒在线观看| 国产精品永久| 亚洲国产一区二区精品专区| 国产欧美亚洲日本| 亚洲精品久久视频| 很黄很黄激情成人| 亚洲视频电影在线| 日韩天天综合| 老司机精品视频网站| 久久国产精品久久久久久电车| 欧美久久久久免费| 亚洲成在人线av| 狠狠色狠狠色综合人人| 亚洲伊人一本大道中文字幕| 日韩一区二区精品视频| 夜夜夜精品看看| 久久国产一区| 欧美一区二区三区的| 欧美日韩蜜桃| 亚洲福利视频专区| 在线日韩av片| 久久久久久久久久久久久女国产乱 | 欧美日韩1区2区| 欧美福利一区| 亚洲国产精品电影| 久久久亚洲精品一区二区三区 | 亚洲乱码视频| 亚洲精品视频在线播放| 欧美不卡视频| 亚洲福利国产| 亚洲免费观看在线视频| 免费成人高清在线视频| 欧美mv日韩mv国产网站app| 韩日成人在线| 免费不卡视频| 亚洲老司机av| 亚洲伊人观看| 国产欧美日韩一区二区三区在线观看| 亚洲欧美日韩综合| 久久福利视频导航| 国产亚洲欧美另类一区二区三区| 欧美影院精品一区| 欧美不卡视频一区| 亚洲精品一线二线三线无人区| 欧美福利影院| 中日韩高清电影网| 久久久99久久精品女同性| 国内外成人免费激情在线视频| 久久精品国产欧美激情| 欧美成人免费va影院高清| 亚洲三级免费观看| 国产精品观看| 久久国产精品久久精品国产| 欧美大片免费观看| 亚洲一区二区三区四区五区午夜| 国产精品爱久久久久久久| 午夜精品久久久久久久男人的天堂 | 日韩一区二区精品| 欧美资源在线观看| 亚洲激情视频在线观看| 欧美午夜国产| 久久精品国产亚洲aⅴ| 欧美成人精品影院| 亚洲小说欧美另类社区| 国产视频久久| 欧美福利视频网站| 午夜精品国产| 亚洲第一精品夜夜躁人人爽| 亚洲一区二区三区成人在线视频精品| 国产精品综合久久久| 久久久国产精品亚洲一区| 日韩视频中文| 国产日韩av一区二区| 免费不卡视频| 性欧美videos另类喷潮| 亚洲第一网站| 亚洲欧美999| 亚洲激情综合| 激情婷婷久久| 国产女人aaa级久久久级| 欧美chengren| 欧美在线高清| 一区二区三区四区五区在线| 久久久精品欧美丰满| 这里只有精品丝袜| 亚洲国产mv| 国产午夜精品理论片a级大结局| 女人色偷偷aa久久天堂| 久久精品一本| 午夜精品视频在线| 亚洲免费观看高清在线观看| 亚洲成人在线视频播放| 久久中文欧美| 性欧美大战久久久久久久久| 日韩视频中文字幕| 亚洲精品日日夜夜| 91久久在线观看| 亚洲国产另类 国产精品国产免费| 国产精品自拍网站| 国产精品国产三级国产普通话三级 | 国产精品裸体一区二区三区| 欧美精品在线观看91| 欧美 日韩 国产 一区| 久久女同互慰一区二区三区| 久久成人综合网| 欧美性一区二区| 欧美日韩亚洲国产精品| 欧美精品一区在线播放| 欧美国产欧美亚洲国产日韩mv天天看完整 | 亚洲国产综合视频在线观看| 麻豆av一区二区三区久久| 久久国产天堂福利天堂| 亚洲在线一区二区三区| 亚洲视频一二| 亚洲免费小视频| 亚洲欧美久久久| 欧美专区在线| 久久久视频精品| 欧美99久久| 亚洲国产91| 亚洲乱码国产乱码精品精 | 亚洲国产成人精品视频| 亚洲国产岛国毛片在线| 亚洲日本免费| 在线亚洲精品| 午夜在线观看欧美| 久久久九九九九| 美女视频黄 久久| 欧美激情无毛| 国产精品极品美女粉嫩高清在线| 国产麻豆一精品一av一免费| 国产主播喷水一区二区| 在线观看欧美激情| 99精品视频一区二区三区| 亚洲深夜影院| 久久激情视频久久| 欧美丰满高潮xxxx喷水动漫| 亚洲免费视频在线观看| 久久久一区二区三区| 亚洲电影免费| 亚洲视频久久| 久久精品视频在线观看| 欧美日韩国产精品自在自线| 国产精品入口福利| 亚洲国产成人在线播放| 亚洲欧美国产毛片在线| 久久久久一区二区三区| 亚洲精品日韩在线观看| 午夜精品亚洲| 欧美日本一区二区三区| 国产亚洲a∨片在线观看| 亚洲精品免费看| 久久久久.com| 亚洲乱码精品一二三四区日韩在线 | 久久亚洲视频| 99精品福利视频| 狼狼综合久久久久综合网 | 国模叶桐国产精品一区| 一本色道精品久久一区二区三区| 久久久久99精品国产片| 日韩视频在线一区二区| 久久伊人亚洲| 国产一区清纯| 亚洲制服av| 亚洲美女色禁图| 欧美丰满高潮xxxx喷水动漫| 国产最新精品精品你懂的| 亚洲一区二区免费在线| 亚洲日本中文字幕|