• <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>

            首先,Orz一下AHdoc神犇,本沙茶是看他的總結(jié)才搞懂劃分樹(shù)的。
            原文地址

            劃分樹(shù),就是每個(gè)結(jié)點(diǎn)代表一個(gè)序列,設(shè)這個(gè)序列的長(zhǎng)度為len,若len>1,則序列中前l(fā)en/2(上取整,這里數(shù)學(xué)公式不好打,真囧)個(gè)元素被分到左子結(jié)點(diǎn),左子結(jié)點(diǎn)代表的序列就是這些元素按照根結(jié)點(diǎn)中的順序組成的序列,而剩下的元素被分到右子結(jié)點(diǎn),右子結(jié)點(diǎn)代表的序列也就是剩下的元素按照根結(jié)點(diǎn)中的順序組成的序列;若len=1,則該結(jié)點(diǎn)為葉結(jié)點(diǎn)。劃分樹(shù)最重要的應(yīng)用就是找區(qū)間第K?。ㄖ皇遣檎?,不包括修改)。

            寫劃分樹(shù)時(shí)主要有兩個(gè)函數(shù):建樹(shù)和找區(qū)間第K小。由于后者AHdoc神犇已經(jīng)總結(jié)了,所以這里只總結(jié)建樹(shù)的函數(shù)。

            設(shè)目前結(jié)點(diǎn)為[l..r](l<r,就是目前的結(jié)點(diǎn)是原序列不斷劃分后得到[l..r]這一段,其實(shí)也就是a0[l..r]打亂順序后得到的,a0為原序列遞增排序后的序列)首先找到中值,就是a0[mid],mid=l+r>>1。然后可以得到,[l..r]中小于中值的的元素一定被劃分到左子結(jié)點(diǎn),[l..r]中大于中值的元素一定被劃分到右子結(jié)點(diǎn),而[l..r]中等于中值的元素則不確定,有的被劃分到左子結(jié)點(diǎn),有的被劃分到右子結(jié)點(diǎn),這就需要先找到應(yīng)被劃分到左子結(jié)點(diǎn)的等于中值的元素個(gè)數(shù)sm(從mid開(kāi)始不斷往左,直到找到邊界處或者找到一個(gè)小于中值的元素為止,或者說(shuō),sm就是a0[l..mid]中等于中值的元素個(gè)數(shù)),然后開(kāi)始劃分,小于中值分到左子結(jié)點(diǎn),大于中值分到右子結(jié)點(diǎn),等于中值的,若目前還沒(méi)滿sm則分到左子結(jié)點(diǎn)否則分到右子結(jié)點(diǎn)。另外中間有兩個(gè)值需要記錄(找區(qū)間第K小時(shí)必須要用到):sl和sr。sl[i]表示[l..i]中被分到左子結(jié)點(diǎn)的元素個(gè)數(shù),sr[i]表示[l..i]中被分到右子結(jié)點(diǎn)的元素個(gè)數(shù)(這里l<=i<=r。顯然sl[i]+sr[i]=i-l+1,其實(shí)sr[i]可以不用記錄的,這里只是為了在找第K小操作中減少計(jì)算次數(shù),起到空間換時(shí)間的作用)。
            建樹(shù)代碼:
            int mkt(int l, int r, int d)
            {
                T[
            ++No].l = l; T[No].r = r; int mid = l + r >> 1; T[No].mid = mid;
                
            if (l == r) return No;
                
            int midv = a0[mid], sm = mid - l + 1; rre2(i, mid, l) if (a0[i] < midv) {sm = mid - i; break;}
                
            int x = l, y = mid + 1;
                
            if (a[d][l] < midv) {
                    a[d 
            + 1][x++= a[d][l]; sl[d][l] = 1; sr[d][l] = 0;
                } 
            else if (a[d][l] == midv && sm) {
                    a[d 
            + 1][x++= a[d][l]; sl[d][l] = 1; sr[d][l] = 0; sm--;
                } 
            else {
                    a[d 
            + 1][y++= a[d][l]; sl[d][l] = 0; sr[d][l] = 1;
                }
                re3(i, l
            +1, r) {
                    
            if (a[d][i] < midv) {
                        a[d 
            + 1][x++= a[d][i]; sl[d][i] = sl[d][i - 1+ 1; sr[d][i] = sr[d][i - 1];
                    } 
            else if (a[d][i] == midv && sm) {
                        a[d 
            + 1][x++= a[d][i]; sl[d][i] = sl[d][i - 1+ 1; sr[d][i] = sr[d][i - 1]; sm--;
                    } 
            else {
                        a[d 
            + 1][y++= a[d][i]; sl[d][i] = sl[d][i - 1]; sr[d][i] = sr[d][i - 1+ 1;
                    }
                }
                
            int No0 = No; T[No0].lch = mkt(l, mid, d + 1); T[No0].rch = mkt(mid + 1, r, d + 1); return No0;
            }
            這里a是每層劃分后的序列。
            查找區(qū)間第K小的代碼:
            int Find_Kth(int l, int r, int K)
            {
                
            int i = root, d = 0, l0, r0, mid0, s0, s1;
                
            while (1) {
                    l0 
            = T[i].l, r0 = T[i].r; mid0 = T[i].mid;
                    
            if (l0 == r0) return a[d][l];
                    
            if (l == l0) s0 = l; else s0 = l0 + sl[d][l - 1]; s1 = l0 + sl[d][r] - 1;
                    
            if (K <= s1 - s0 + 1) {
                        i 
            = T[i].lch; l = s0; r = s1; d++;
                    } 
            else {
                        K 
            -= (s1 - s0 + 1); if (l == l0) l = mid0 + 1else l = mid0 + sr[d][l - 1+ 1;
                        r 
            = mid0 + sr[d][r]; i = T[i].rch; d++;
                    }
                }
            }

            【具體題目】PKU2104、PKU2761(兩道任選一道)

            posted @ 2011-06-27 20:54 Mato_No1 閱讀(2860) | 評(píng)論 (1)編輯 收藏

            (1)Robotic Sort(HDU1890、ZJU2985
            本題主要考察的是對(duì)此類問(wèn)題,序列中給定值的索引問(wèn)題。
            當(dāng)Splay Tree用來(lái)處理一個(gè)序列的時(shí)候,其關(guān)鍵字就是序列中元素的下標(biāo),而不是元素的值。這樣,如果要查找序列中給定的值的位置(假設(shè)序列中任意兩個(gè)元素的值不相等)看起來(lái)就無(wú)法實(shí)現(xiàn)。其實(shí)也是有辦法實(shí)現(xiàn)的:因?yàn)樵卦跇?shù)中的下標(biāo)是永遠(yuǎn)不變的!也就是,設(shè)這個(gè)序列為A,如果A[i]在插入Splay Tree時(shí)的下標(biāo)為j,那么在A[i]被刪除之前,其下標(biāo)一直是j,永遠(yuǎn)不會(huì)變(注意元素在序列中的下標(biāo)和在樹(shù)中的下標(biāo)是不同的)。利用這一性質(zhì)可以解決給定值的索引問(wèn)題。
            對(duì)于本題,每次將從序列中第i小的元素到序列中第i個(gè)元素(這里假定元素下標(biāo)是從1開(kāi)始的,而不是從0開(kāi)始)之間的所有元素反轉(zhuǎn),并輸出第i小的元素在反轉(zhuǎn)之前的在序列中的下標(biāo)。設(shè)B[i]為第i小的數(shù)的初始位置,S[i]為初始位置在第i位的元素的下標(biāo),B[i]和S[i]都可以通過(guò)預(yù)處理得到。然后,每次交換前,求出S[B[i]]在樹(shù)中的排名(基本操作),再減1(因?yàn)橛羞吔缃Y(jié)點(diǎn))就是該元素目前的位置,而序列中第i個(gè)元素就是樹(shù)中第(i+1)小的元素,再執(zhí)行交換即可。
            不過(guò)需要千萬(wàn)注意的是本題的標(biāo)記問(wèn)題,在求S[B[i]]在樹(shù)中的排名時(shí),有可能其祖先結(jié)點(diǎn)上有標(biāo)記,需要先遍歷其所有的祖先結(jié)點(diǎn),再逆向下放標(biāo)記(標(biāo)記只能自頂向下傳)。另外在交換的時(shí)候需要求出S[B[i]]的后繼,在求后繼的過(guò)程中需要查找,此時(shí)千萬(wàn)別忘了下放標(biāo)記(總的說(shuō),凡是有查找的地方,就有dm)
            代碼(本沙茶太弱了,是抄別人的,因此其算法和上面總結(jié)的可能有差別,神犇不要鄙視)

            (2)SuperMemo(PKU3580
            本題的6個(gè)操作中,add、reverse、insert、delete、min都不難搞,而revolve操作需要涉及到區(qū)間交換。
            可以發(fā)現(xiàn),所謂的旋轉(zhuǎn)其實(shí)就是交換兩個(gè)相鄰區(qū)間,這對(duì)于功能極強(qiáng)的Splay Tree來(lái)說(shuō)根本不難搞。
            設(shè)這兩個(gè)相鄰區(qū)間為[x, y]與[y+1, z],假設(shè)它們均非空(也就是x<=y<z,因?yàn)槿羝渲兄辽儆幸粋€(gè)區(qū)間是空區(qū)間,則交換沒(méi)有意義),先找到樹(shù)中x的前趨P與z的后繼S(這里x、z等指的都是對(duì)應(yīng)的結(jié)點(diǎn),下同),將P伸展到根、將S伸展到根的右子結(jié)點(diǎn)處,則S的左子樹(shù)就表示區(qū)間[x, z]。然后,設(shè)S的左子樹(shù)的根結(jié)點(diǎn)(也就是S的左子結(jié)點(diǎn))為N,在這棵子樹(shù)中找到第1小的結(jié)點(diǎn)P0與第(y-x+2)小的結(jié)點(diǎn)S0(這需要涉及到找子樹(shù)內(nèi)第K小的操作,只要把找整棵樹(shù)第K小的操作的root改為N即可),它們分別表示x與(y+1),這樣將P0伸展到N處,將S0伸展到N的右子結(jié)點(diǎn)處,顯然P0無(wú)左子樹(shù),S0的左子樹(shù)T1表示區(qū)間[x+1, y],S0的右子樹(shù)T2表示區(qū)間[y+2, z]。然后,先將S0從P0的右子結(jié)點(diǎn)移動(dòng)到P0的左子結(jié)點(diǎn),再將T1作為P0的右子樹(shù)(注意移動(dòng)是兩步:插入和刪除)。這樣整棵子樹(shù)的中序遍歷結(jié)果變成了S0->T2->P0->T1,也就是[y+1, z]∪[x, y]。
            另外本題的標(biāo)記有點(diǎn)難搞,只需注意rev是逆向標(biāo)記,以及查找與dm共存就行了。
            代碼
            (3)Play with Chain(HDU3487)
            這個(gè)米有神馬好說(shuō)的,里面的操作在SuperMemo里都有;
            代碼
            (4)AHOI2006 文本編輯器(editor)
            這題在這一類題里面算水的。對(duì)于光標(biāo),只要用一個(gè)值來(lái)維護(hù)就行了。
            另外注意本題極為猥瑣的2點(diǎn)(題目中米有說(shuō)明):一是最初的文本并不是空的,而是有一個(gè)空格;二是執(zhí)行GET操作時(shí)光標(biāo)可能位于文本末尾,此時(shí)應(yīng)輸出空格;
            代碼
            (5)HFTSC2011 高精度計(jì)算器(apc),題目見(jiàn)這個(gè)帖子;
            這題反映出了一個(gè)很囧的問(wèn)題:有些信息會(huì)被rev整體破壞。
            本題中的操作都是常見(jiàn)操作,但是本題所需要維護(hù)的信息卻不是那么容易維護(hù)。本題要求維護(hù)一棵子樹(shù)中所有結(jié)點(diǎn)所表示的元素序列(中序遍歷結(jié)果)模一個(gè)指定的M的值。設(shè)F[i]為R^i mod M的值(這里R是進(jìn)制,也就是題目中的K),則有:
            T[x].mod = (T[T[x].c[0]].mod * F[T[T[x].c[1]].sz + 1] + T[x].v * F[T[T[x].c[1]].sz] + T[T[x].c[1]].mod) % M;
            這個(gè)式子其實(shí)是很好理解的。
            關(guān)鍵是,本題的猥瑣之處并不在此。注意本題的rev操作,它會(huì)整體改變樹(shù)中所有結(jié)點(diǎn)所記錄的mod值,這時(shí),如果再用上面這個(gè)式子來(lái)維護(hù)T[x].mod,就會(huì)出錯(cuò),因?yàn)榇藭r(shí)引用到的T[T[x].c[0]].mod和T[T[x].c[1]].mod都是過(guò)時(shí)的。解決這一問(wèn)題只有一種方法:記錄“逆mod”(rmod),意思是將整個(gè)元素序列反轉(zhuǎn)后的mod,即:
            T[x].rmod = (T[T[x].c[1]].rmod * F[T[T[x].c[0]].sz + 1] + T[x].v * F[T[T[x].c[0]].sz] + T[T[x].c[0]].rmod) % M;
            這樣,在反轉(zhuǎn)某序列的時(shí)候,直接將根結(jié)點(diǎn)的mod值和rmod值交換就行了。
            像mod這樣會(huì)被反轉(zhuǎn)操作整體破壞的信息還有很多,比如NOI2005 sequence里面的lmax和rmax。如果真的遇到這類信息,只有采用上面的方法。
            另外,本題第6、9、10個(gè)點(diǎn)有誤。
            代碼

            現(xiàn)在Splay Tree差不多弄完了,接下來(lái)還有N多知名的、不知名的高級(jí)數(shù)據(jù)結(jié)構(gòu)……時(shí)間MS有些不夠用了囧……

            posted @ 2011-06-25 11:21 Mato_No1 閱讀(5074) | 評(píng)論 (2)編輯 收藏

            平衡樹(shù)操作,尤其是Splay Tree的區(qū)間操作(當(dāng)線段樹(shù)用的那種),代碼長(zhǎng)且極易搞疵,當(dāng)操作多的時(shí)候,甚至可能調(diào)試的時(shí)間比寫代碼的時(shí)間都長(zhǎng)!
            這里來(lái)總結(jié)一下平衡樹(shù)在實(shí)際應(yīng)用(編程)中的易疵點(diǎn)和調(diào)試技巧。
            【平衡樹(shù)(這里主要指Splay Tree)的操作】
            這個(gè)MS前面已經(jīng)總結(jié)過(guò)了,主要分以下幾類:
            (1)基本操作:查找、單結(jié)點(diǎn)插入、單結(jié)點(diǎn)刪除、找第K小、求結(jié)點(diǎn)的排名、找指定結(jié)點(diǎn)的前趨和后繼;
            (2)進(jìn)階操作:找給定的值在樹(shù)中的前趨和后繼、對(duì)一個(gè)序列建平衡樹(shù)、插入整棵子樹(shù)、刪除整棵子樹(shù);
            (3)區(qū)間操作(與線段樹(shù)的結(jié)合):標(biāo)記(自頂向下);
            (4)區(qū)間操作(與線段樹(shù)的結(jié)合):最大值、總和等自底向上維護(hù)的信息;
            (5)一些特別猥瑣的操作(比如PKU3580的revolve);
            【主要函數(shù)及其易疵點(diǎn)】
            (1)void sc(int _p, int _c, bool _d);
            將_p的_d子結(jié)點(diǎn)(0左1右)置為_(kāi)c,這個(gè)就三句話,應(yīng)該不會(huì)搞疵;
            (2)void upd(int x);
            更新x記錄的一些信息(自底向上維護(hù)的信息,包括大小域sz),這個(gè),有幾個(gè)信息就寫幾句話就行了,不易搞疵;
            (3)void rot(int x);
            旋轉(zhuǎn)操作,將x旋轉(zhuǎn)到其父結(jié)點(diǎn)的位置。這個(gè)函數(shù)中有兩個(gè)易疵點(diǎn):一是若x的父結(jié)點(diǎn)為根,則需要在旋轉(zhuǎn)后將根置為x,且將x的父結(jié)點(diǎn)置為0(特別注意這個(gè));二是若x的父結(jié)點(diǎn)不為根,則需要將x的父結(jié)點(diǎn)的父結(jié)點(diǎn)的對(duì)應(yīng)子結(jié)點(diǎn)(原來(lái)是x的父結(jié)點(diǎn)的那個(gè))置為x;另外旋轉(zhuǎn)完成后別忘了更新;
            (4)void splay(int x, int r);
            伸展操作,將x伸展到r的子結(jié)點(diǎn)處,這個(gè)操作是最核心的操作,只要記住了過(guò)程,且rot不搞疵,這個(gè)也就不會(huì)搞疵;
            (5)void dm(int x);
            對(duì)于有標(biāo)記的結(jié)點(diǎn)的下放標(biāo)記操作,極易疵!
            首先,對(duì)于任何結(jié)點(diǎn),標(biāo)記一打上就必須立即生效,因此除了將x的標(biāo)記取消、x的兩個(gè)子結(jié)點(diǎn)(如果存在的話)打上標(biāo)記外,還要更新兩個(gè)子結(jié)點(diǎn)的一些域(當(dāng)然這里更新千萬(wàn)不能用upd更新);
            其次,要搞清楚x的每一個(gè)標(biāo)記的類型,是疊加型標(biāo)記(如整體加上某一個(gè)數(shù)的標(biāo)記)、覆蓋型標(biāo)記(如整體置為某一個(gè)數(shù)的標(biāo)記)還是逆向標(biāo)記(如是否翻轉(zhuǎn)過(guò)的標(biāo)記,因?yàn)榉D(zhuǎn)兩次等于沒(méi)轉(zhuǎn)),然后在下放標(biāo)記的時(shí)候注意寫法(因?yàn)閤的子結(jié)點(diǎn)原來(lái)可能也有標(biāo)記,此時(shí)只有覆蓋型標(biāo)記需要將x的子結(jié)點(diǎn)的原來(lái)的標(biāo)記覆蓋掉,對(duì)于疊加型標(biāo)記,應(yīng)為加法運(yùn)算,對(duì)于逆向標(biāo)記,應(yīng)為取反操作,還有其它類型的標(biāo)記分別處理);
            最后還有一點(diǎn),就是x的左子結(jié)點(diǎn)或右子結(jié)點(diǎn)可能不存在(0),此時(shí)就不能對(duì)這里的0號(hào)結(jié)點(diǎn)下放標(biāo)記。
            (6)int Find_Kth(int x, int K);
            在以結(jié)點(diǎn)x為根的子樹(shù)中找第K小的結(jié)點(diǎn),返回結(jié)點(diǎn)編號(hào);若省略x,默認(rèn)x=root(即在整棵樹(shù)中找);
            這個(gè)一般不會(huì)搞疵,注意兩個(gè)地方即可:一是有mul域的時(shí)候需要考慮mul域,二是結(jié)點(diǎn)如果有標(biāo)記,需要在找的時(shí)候下放標(biāo)記。(凡是查找操作,包括插入新結(jié)點(diǎn)時(shí)的查找,在查找過(guò)程中必須不斷下放標(biāo)記,因?yàn)椴檎抑蠛芸赡芫鸵煺梗?br />(7)void ins(int _v);
                   void ins(int x, int _v)
            插入一個(gè)值為_(kāi)v的結(jié)點(diǎn)。前者是普通插入,后者是用Splay Tree處理序列(當(dāng)線段樹(shù)用)時(shí)的插入,表示在第x小的結(jié)點(diǎn)后插入;
            前者注意有三種情況:樹(shù)為空;樹(shù)非空且值為_(kāi)v的結(jié)點(diǎn)不存在;樹(shù)非空且值為_(kāi)v的結(jié)點(diǎn)已存在;
            后者需要注意,在將第x小的結(jié)點(diǎn)伸展到根,將第(x+1)小的結(jié)點(diǎn)伸展到根的右子結(jié)點(diǎn),再插入后,需要upd上述兩個(gè)結(jié)點(diǎn)(注意upd順序);
            (8)void del(int x);
            將結(jié)點(diǎn)x刪除。這個(gè)一般不會(huì)搞疵,唯一要注意的一點(diǎn)是刪除后的upd;
            (9)打標(biāo)記操作(add、reverse、makesame等):
            其實(shí)打標(biāo)記操作的代碼量是很少的,關(guān)鍵就是對(duì)于不同標(biāo)記要分別處理,同(5);
            (10)各種詢問(wèn)操作:
            這個(gè)直接調(diào)出相應(yīng)的域即可,幾乎不可能搞疵;
            (11)極其猥瑣的操作(revolve等):
            這個(gè)屬于最易疵的操作(否則就體現(xiàn)不出其猥瑣了):
            首先要搞清楚這個(gè)操作的具體過(guò)程,不要連具體過(guò)程都搞疵了,這樣寫出來(lái)的代碼肯定是疵的;
            然后,注意一些非常猥瑣的細(xì)節(jié)問(wèn)題(很多時(shí)候,本沙茶都是栽在細(xì)節(jié)上的);
            另外,這些操作中一般都會(huì)出現(xiàn)多次伸展,別忘了伸展后的upd;
            (12)其它易疵點(diǎn):
            [1]“移動(dòng)”是由“插入”和“刪除”兩部分組成的,因此凡是涉及到“移動(dòng)”類的操作(如將某棵子樹(shù)或某個(gè)結(jié)點(diǎn)移動(dòng)到另一個(gè)位置等),必須要同時(shí)出現(xiàn)“插入”和“刪除”,而不能只插入不刪除;
            [2]注意檢查主函數(shù)main()中是否有搞疵的地方;

            易疵點(diǎn)也就這么多了,下面是調(diào)試技巧:
            【Splay Tree調(diào)試技巧】
            (1)先用樣例調(diào)試,保證樣例能夠通過(guò)(注意,樣例中必須包含所有題目中給出的操作,如果不是這樣,那在調(diào)試完樣例后還要加一組包含所有題目中給出的操作的小數(shù)據(jù));
            (2)然后用mkdt造大數(shù)據(jù),一般平衡樹(shù)操作題的合法數(shù)據(jù)都不難造,只要注意別越界就行了;
            (3)在調(diào)試過(guò)程中應(yīng)不斷少量增大數(shù)據(jù)規(guī)模,因?yàn)樵谀苷页鰡?wèn)題的情況下,數(shù)據(jù)規(guī)模越小越好;
            (4)如果調(diào)試過(guò)程中發(fā)現(xiàn)死循環(huán)或者RE:
            [1]首先輸出每個(gè)操作,找出是在哪個(gè)操作處出了問(wèn)題;
            [2]進(jìn)入該操作內(nèi)部檢查,也就是把該操作的代碼讀一遍,一般都能找出問(wèn)題;
            [3]如果找不出問(wèn)題,可以進(jìn)入該操作內(nèi)部跟蹤檢查;
            [4]如果跟蹤檢查仍然沒(méi)找出問(wèn)題,那可能是該操作是對(duì)的,而其它操作出了問(wèn)題,此時(shí)可以弄一個(gè)vst,把整棵樹(shù)遍歷一下,找出真正出問(wèn)題的操作,轉(zhuǎn)[2];
            (5)排除了死循環(huán)或RE后,接下來(lái)就是檢查輸出結(jié)果是否正確了:
            [1]對(duì)于小規(guī)模數(shù)據(jù)可人工計(jì)算檢查,對(duì)于較大規(guī)模數(shù)據(jù)則必須采用對(duì)照方法,即弄一個(gè)模擬法的代碼,保證該代碼正確,然后進(jìn)行對(duì)照;
            [2]如果發(fā)現(xiàn)加入遍歷時(shí),結(jié)果正確,但去掉遍歷后結(jié)果不正確,則表明是處理結(jié)點(diǎn)標(biāo)記的時(shí)候出了問(wèn)題(因?yàn)樵诒闅v過(guò)程中需要下放標(biāo)記),進(jìn)入dm或加標(biāo)記操作中檢查即可;
            [3]其它情況下,可以在遍歷中輸出整個(gè)序列(或整棵樹(shù)),進(jìn)行對(duì)照,找出問(wèn)題所在;
            [4]如果數(shù)據(jù)過(guò)大,操作過(guò)多,人工分析時(shí)間較長(zhǎng),就只能采用讀代碼的方式檢查了;
            (6)最后,用mkdt造一個(gè)最大規(guī)模的數(shù)據(jù),檢查是否TLE、是否越界(數(shù)組是否開(kāi)小);
            如果以上各點(diǎn)全部通過(guò),就可以宣布AC了。

            posted @ 2011-06-23 00:07 Mato_No1 閱讀(667) | 評(píng)論 (2)編輯 收藏

            【原題見(jiàn)這里
            本題是Splay Tree處理序列問(wèn)題(也就是當(dāng)線段樹(shù)用)的一個(gè)典型例題。

            Splay Tree之所以可以當(dāng)線段樹(shù)用,是因?yàn)樗梢灾С忠粋€(gè)序列,然后用“左端前趨伸展到根,右端后繼伸展到根的右子結(jié)點(diǎn),取根的右子結(jié)點(diǎn)的左子結(jié)點(diǎn)”這種伸展方法,對(duì)一個(gè)序列中的一整段進(jìn)行整體操作。由于要防止出現(xiàn)前趨或后繼不存在的情況,需要在這個(gè)序列的兩端加入兩個(gè)邊界結(jié)點(diǎn),要求其值不能影響到結(jié)點(diǎn)各種記載信息的維護(hù)(多取0、∞或-∞)。這兩個(gè)邊界結(jié)點(diǎn)在樹(shù)中永遠(yuǎn)存在,不會(huì)被刪除。

            (1)結(jié)點(diǎn)的引用:
            在當(dāng)線段樹(shù)用的Splay Tree中,真正的關(guān)鍵字是下標(biāo)而不是值,因此,“序列中第i個(gè)結(jié)點(diǎn)”實(shí)際上對(duì)應(yīng)的是“樹(shù)中第(i+1)小的結(jié)點(diǎn)”(因?yàn)樽筮呥€有一個(gè)邊界結(jié)點(diǎn)),這就說(shuō)明在對(duì)結(jié)點(diǎn)引用時(shí)需要找第K小的操作。因此,下面的“結(jié)點(diǎn)x”指的是“樹(shù)中第(x+1)小的結(jié)點(diǎn)”。
            (2)標(biāo)記:
            在線段樹(shù)中,如果對(duì)一個(gè)結(jié)點(diǎn)所表示的線段整體進(jìn)行了某種操作,需要在這個(gè)結(jié)點(diǎn)上打上一個(gè)標(biāo)記,在下一次再找到這個(gè)結(jié)點(diǎn)時(shí),其標(biāo)記就會(huì)下放到其兩個(gè)子結(jié)點(diǎn)上。在Splay Tree中也可以引入標(biāo)記。比如要對(duì)[2, 6]這一段進(jìn)行整體操作,就將結(jié)點(diǎn)1伸展到根的位置,將結(jié)點(diǎn)7伸展到根的右子樹(shù)的位置,然后結(jié)點(diǎn)7的左子樹(shù)就表示[2, 6]這一段,對(duì)這棵子樹(shù)的根結(jié)點(diǎn)打上標(biāo)記并立即生效(必須是立即生效,而不是等下一次引用再生效),也就是立即改變?cè)摻Y(jié)點(diǎn)記錄的一些信息的值。如果下次再次引用到這個(gè)結(jié)點(diǎn),就要將其標(biāo)記下放到其兩個(gè)子結(jié)點(diǎn)處;
            需要注意的一點(diǎn)是,如果要伸展某個(gè)結(jié)點(diǎn)x到r的子結(jié)點(diǎn)的位置,就必須保證從x原來(lái)的位置到r的這個(gè)子結(jié)點(diǎn)(x伸展后的位置)上的所有結(jié)點(diǎn)上均沒(méi)有標(biāo)記,否則就會(huì)導(dǎo)致標(biāo)記混亂。因此,必須首先找到這個(gè)結(jié)點(diǎn)x,在此過(guò)程中不斷下放標(biāo)記。
            (3)自底向上維護(hù)的信息:
            標(biāo)記可以看成一種自頂向下維護(hù)的信息。除了標(biāo)記以外,作為“線段樹(shù)”,往往還要維護(hù)一些自底向上維護(hù)的信息。比如在sequence這題中,就有l(wèi)max(左段連續(xù)最大和)、rmax(右段連續(xù)最大和)、midmax(全段連續(xù)最大和)以及sum(全段總和)等信息要維護(hù)。對(duì)于這類東東其實(shí)也很好辦,因?yàn)樽訕?shù)大?。╯z域)就是一種自底向上維護(hù)的信息,因此對(duì)于這些信息只要按照維護(hù)sz域的辦法維護(hù)即可(統(tǒng)一寫在upd函數(shù)里)。唯一的不同點(diǎn)是打標(biāo)記時(shí)它們的值可能要改變。
            (4)對(duì)連續(xù)插入的結(jié)點(diǎn)建樹(shù):
            本題的插入不是一個(gè)一個(gè)插入,而是一下子插入一整段,因此需要先將它們建成一棵樹(shù)。一般建樹(shù)操作都是遞歸的,這里也一樣。設(shè)目前要對(duì)A[l..r]建樹(shù)(A為待插入序列),若l>r則退出,否則找到位于中間的元素mid = l + r >> 1,將A[mid]作根,再對(duì)A[l..mid-1]建左子樹(shù),對(duì)A[mid+1..r]建右子樹(shù)即可。這樣可以保證一開(kāi)始建的就是一棵平衡樹(shù),減小常數(shù)因子。
            (5)回收空間:
            根據(jù)本題的數(shù)據(jù)范圍提示,插入的結(jié)點(diǎn)總數(shù)最多可能達(dá)到4000000,但在任何時(shí)刻樹(shù)中最多只有500002個(gè)結(jié)點(diǎn)(包括兩個(gè)邊界),此時(shí)為了節(jié)省空間,可以采用循環(huán)隊(duì)列回收空間的方法。即:一開(kāi)始將所有的可用空間(可用下標(biāo),本題為1~500002)存在循環(huán)隊(duì)列Q里,同時(shí)設(shè)立頭尾指針front和rear,每次如果有新結(jié)點(diǎn)插入,就取出Q[front]并作為新結(jié)點(diǎn)的下標(biāo),如果有結(jié)點(diǎn)要?jiǎng)h除(本題是一次刪除整棵子樹(shù),因此在刪除后需要分別回收它們的空間),則從rear開(kāi)始,將每個(gè)刪除的結(jié)點(diǎn)的下標(biāo)放回到Q里。當(dāng)然,這種方法是要犧牲一定的時(shí)間的,因此在空間不是特別吃緊的情況下不要用。

            【2012年1月16日更新】
            今天重寫sequence的時(shí)候,禿然發(fā)現(xiàn)加入的邊界點(diǎn)可能會(huì)對(duì)lmax、rmax、midmax等的維護(hù)造成影響:當(dāng)序列中所有的值都是負(fù)數(shù)時(shí),若邊界點(diǎn)的值設(shè)為0,將使這3個(gè)值也為0,所以,邊界點(diǎn)的值應(yīng)設(shè)為-INF(不會(huì)影響到sum,因?yàn)榭梢詥为?dú)調(diào)出[l, r]的sum,避開(kāi)邊界)。這就說(shuō)明并非所有這樣的題中都可以設(shè)置邊界點(diǎn)(比如HFTSC2011的那題就不行),如果邊界點(diǎn)會(huì)對(duì)維護(hù)的信息造成影響,就不能設(shè)置邊界點(diǎn),在各個(gè)操作中,分4種情況判斷。(代碼已經(jīng)修改)

            下面上代碼了:
            #include <iostream>
            #include 
            <stdio.h>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re1(i, n) for (int i=1; i<=n; i++)
            const int MAXN = 500002, NOSM = -2000, INF = ~0U >> 2;
            struct node {
                
            int v, c[2], p, sz, sum, lmax, rmax, midmax, sm;
                
            bool rev, d;
            } T[MAXN 
            + 1];
            int root, Q[MAXN + 1], front, rear, a[MAXN], len, res;
            int max(int SS0, int SS1)
            {
                
            return SS0 >= SS1 ? SS0 : SS1;
            }
            int max(int SS0, int SS1, int SS2)
            {
                
            int M0 = SS0 >= SS1 ? SS0 : SS1; return M0 >= SS2 ? M0 : SS2;
            }
            void newnode(int n, int _v)
            {
                T[n].v 
            = T[n].sum = T[n].lmax = T[n].rmax = T[n].midmax = _v; T[n].c[0= T[n].c[1= 0; T[n].sz = 1; T[n].sm = NOSM; T[n].rev = 0;
            }
            void sc(int _p, int _c, bool _d)
            {
                T[_p].c[_d] 
            = _c; T[_c].p = _p; T[_c].d = _d;
            }
            void sm_opr(int x, int SM)
            {
                T[x].sum 
            = T[x].sz * SM;
                
            if (SM > 0) T[x].lmax = T[x].rmax = T[x].midmax = T[x].sum; else T[x].lmax = T[x].rmax = T[x].midmax = SM;
            }
            void rev_opr(int x)
            {
                
            int c0 = T[x].c[0], c1 = T[x].c[1]; sc(x, c0, 1); sc(x, c1, 0);
                
            int tmp = T[x].lmax; T[x].lmax = T[x].rmax; T[x].rmax = tmp;
            }
            void dm(int x)
            {
                
            int SM0 = T[x].sm;
                
            if (SM0 != NOSM) {
                    T[x].v 
            = T[T[x].c[0]].sm = T[T[x].c[1]].sm = SM0; T[x].sm = NOSM;
                    sm_opr(T[x].c[
            0], SM0); sm_opr(T[x].c[1], SM0);
                }
                
            if (T[x].rev) {
                    T[T[x].c[
            0]].rev = !T[T[x].c[0]].rev; T[T[x].c[1]].rev = !T[T[x].c[1]].rev; T[x].rev = 0;
                    rev_opr(T[x].c[
            0]); rev_opr(T[x].c[1]);
                }
            }
            void upd(int x)
            {
                
            int c0 = T[x].c[0], c1 = T[x].c[1];
                T[x].sz 
            = T[c0].sz + T[c1].sz + 1;
                T[x].sum 
            = T[c0].sum + T[c1].sum + T[x].v;
                T[x].lmax 
            = max(T[c0].lmax, T[c0].sum + T[x].v + max(T[c1].lmax, 0));
                T[x].rmax 
            = max(T[c1].rmax, max(T[c0].rmax, 0+ T[x].v + T[c1].sum);
                T[x].midmax 
            = max(T[c0].midmax, T[c1].midmax, max(T[c0].rmax, 0+ T[x].v + max(T[c1].lmax, 0));
            }
            void rot(int x)
            {
                
            int y = T[x].p; bool d = T[x].d;
                
            if (y == root) {root = x; T[root].p = 0;} else sc(T[y].p, x, T[y].d);
                sc(y, T[x].c[
            !d], d); sc(x, y, !d); upd(y);
            }
            void splay(int x, int r)
            {
                
            int p; while ((p = T[x].p) != r) if (T[p].p == r) rot(x); else if (T[x].d == T[p].d) {rot(p); rot(x);} else {rot(x); rot(x);} upd(x);
            }
            int Find_Kth(int K)
            {
                
            int i = root, S0;
                
            while (i) {
                    dm(i); S0 
            = T[T[i].c[0]].sz + 1;
                    
            if (K == S0) breakelse if (K < S0) i = T[i].c[0]; else {K -= S0; i = T[i].c[1];}
                }
                
            return i;
            }
            int mkt(int l, int r)
            {
                
            if (l > r) return 0;
                
            int n0 = Q[front], mid = l + r >> 1if (front == MAXN) front = 1else front++;
                newnode(n0, a[mid]); 
            int l_r = mkt(l, mid - 1), r_r = mkt(mid + 1, r);
                sc(n0, l_r, 
            0); sc(n0, r_r, 1); upd(n0); return n0;
            }
            void ins(int pos)
            {
                
            int P0 = Find_Kth(pos); splay(P0, 0); int P1 = Find_Kth(pos + 1); splay(P1, root); sc(P1, mkt(0, len - 1), 0); upd(P1); upd(P0);
            }
            void era(int x)
            {
                
            if (!x) return;
                
            if (rear == MAXN) rear = 1else rear++; Q[rear] = x;
                era(T[x].c[
            0]); era(T[x].c[1]);
            }
            void del(int l, int r)
            {
                
            int P0 = Find_Kth(l - 1); splay(P0, 0); int P1 = Find_Kth(r + 1); splay(P1, root); 
                
            int root0 = T[P1].c[0]; sc(P1, 00); upd(P1); upd(P0); era(root0);
            }
            void mksame(int l, int r, int x)
            {
                
            int P0 = Find_Kth(l - 1); splay(P0, 0); int P1 = Find_Kth(r + 1); splay(P1, root); 
                
            int n = T[P1].c[0]; T[n].sm = x; sm_opr(n, x); upd(P1); upd(P0);
            }
            void reve(int l, int r)
            {
                
            int P0 = Find_Kth(l - 1); splay(P0, 0); int P1 = Find_Kth(r + 1); splay(P1, root); 
                
            int n = T[P1].c[0]; T[n].rev = !T[n].rev; rev_opr(n); upd(P1); upd(P0);
            }
            int get_sum(int l, int r)
            {
                
            int P0 = Find_Kth(l - 1); splay(P0, 0); int P1 = Find_Kth(r + 1); splay(P1, root); 
                
            int n = T[P1].c[0]; return T[n].sum;
            }
            int max_sum()
            {
                
            return T[root].midmax;
            }
            void prepare()
            {
                T[
            0].sz = T[0].sum = T[0].lmax = T[0].rmax = T[0].midmax = 0;
                front 
            = 3; rear = MAXN; re1(i, MAXN) Q[i] = i;
                newnode(
            1-INF); newnode(2-INF); sc(121); root = 1; T[root].p = 0;
            }
            int main()
            {
                freopen(
            "sequence.in""r", stdin);
                freopen(
            "sequence.out""w", stdout);
                prepare();
                
            int m, l, r, x;
                scanf(
            "%d%d"&len, &m); char ch = getchar(), str[1000];
                re(i, len) scanf(
            "%d"&a[i]); ins(1);
                re(i, m) {
                    scanf(
            "%s", str);
                    
            if (!strcmp(str, "INSERT")) {scanf("%d%d"&l, &len); re(i, len) scanf("%d"&a[i]); ins(++l);}
                    
            if (!strcmp(str, "DELETE")) {scanf("%d%d"&l, &r); r += l++; del(l, r);}
                    
            if (!strcmp(str, "MAKE-SAME")) {scanf("%d%d%d"&l, &r, &x); r += l++; mksame(l, r, x);}
                    
            if (!strcmp(str, "REVERSE")) {scanf("%d%d"&l, &r); r += l++; reve(l, r);}
                    
            if (!strcmp(str, "GET-SUM")) {scanf("%d%d"&l, &r); r += l++; printf("%d\n", get_sum(l, r));}
                    
            if (!strcmp(str, "MAX-SUM")) printf("%d\n", max_sum());
                    ch 
            = getchar();
                }
                fclose(stdin); fclose(stdout);
                
            return 0;
            }

            最后把我的這個(gè)代碼與BYVoid神犇的本題代碼進(jìn)行測(cè)試比較,結(jié)果(BYVoid神犇的代碼見(jiàn)這里):

            BYVoid神犇的:


            本沙茶的:


            【相關(guān)論文】
            運(yùn)用伸展樹(shù)解決數(shù)列維護(hù)問(wèn)題 by JZP
            【感謝】
            JZP神犇(提供論文)
            BYVoid神犇(提供標(biāo)程)

            posted @ 2011-06-21 16:06 Mato_No1 閱讀(2134) | 評(píng)論 (0)編輯 收藏

            額……最近兩天光顧著刷題忘了總結(jié)了……正好現(xiàn)在把總結(jié)的東東全部補(bǔ)上吧囧……

            【重復(fù)次數(shù)mul】
            在前面第一篇總結(jié)Splay Tree的時(shí)候就提到了結(jié)點(diǎn)的重復(fù)次數(shù)(mul)域,這個(gè)東東至今在網(wǎng)上還米看見(jiàn)有犇用(在本沙茶見(jiàn)過(guò)的范圍內(nèi)),但是不可否認(rèn)的是這個(gè)域在某些情況下幾乎是“必須使用”的。
            所謂重復(fù)次數(shù),就是將樹(shù)中所有值(v)相同的結(jié)點(diǎn)全部合并成一個(gè),這些結(jié)點(diǎn)的總個(gè)數(shù)就是合并后的結(jié)點(diǎn)的mul值。比如,在一棵空樹(shù)中插入3個(gè)值為5的結(jié)點(diǎn)后,在使用mul域的情況下,樹(shù)中只有一個(gè)結(jié)點(diǎn),其v值為5,mul值為3。
            在平衡樹(shù)中,值相同的結(jié)點(diǎn)確實(shí)非常礙事。按照二叉查找樹(shù)的定義:“要么是一棵空樹(shù),要么是一棵滿足以下條件的非空樹(shù):根結(jié)點(diǎn)左子樹(shù)中所有結(jié)點(diǎn)的值均小于根結(jié)點(diǎn),根結(jié)點(diǎn)右子樹(shù)中所有結(jié)點(diǎn)的值均大于根結(jié)點(diǎn),且根的左右子樹(shù)均為二叉查找樹(shù)”,在二叉查找樹(shù)中,是不應(yīng)該出現(xiàn)值相同的結(jié)點(diǎn)的。可是在實(shí)際問(wèn)題中,出現(xiàn)值相同的結(jié)點(diǎn)幾乎是不可避免的。此時(shí),樹(shù)的定義就會(huì)變得非常模糊,也就是要把二叉查找樹(shù)定義中的“小于”全部改為“小于等于”,“大于”全部改為“大于等于”。這樣定義的樹(shù)在一般情況下也米有神馬問(wèn)題,但是在找前趨(Pred)和找后繼(Succ)操作中,問(wèn)題就來(lái)了。因?yàn)楦Y(jié)點(diǎn)的前趨和后繼的值可能與根結(jié)點(diǎn)相等(比如 HNOI2004 寵物收養(yǎng)所 那一題),這時(shí),根結(jié)點(diǎn)的前趨既有可能在左子樹(shù)里,也有可能在右子樹(shù)里,根結(jié)點(diǎn)的后繼也一樣。此時(shí),這兩個(gè)操作就無(wú)法進(jìn)行了。
            【mul域的維護(hù)】
            mul域其實(shí)可以看成結(jié)點(diǎn)的一個(gè)本身屬性,和v一樣。因此在旋轉(zhuǎn)、伸展操作中任何結(jié)點(diǎn)的mul值都是不會(huì)改變的??赡芨淖兘Y(jié)點(diǎn)mul值的地方只有兩處:一是插入,二是刪除。在插入一個(gè)值為_(kāi)v的結(jié)點(diǎn)的時(shí)候,如果發(fā)現(xiàn)值為_(kāi)v的結(jié)點(diǎn)在樹(shù)中已經(jīng)存在,則只會(huì)將這個(gè)結(jié)點(diǎn)的mul值加1,而不是真正插入一個(gè)新的結(jié)點(diǎn)。同樣的,在刪除一個(gè)結(jié)點(diǎn)的時(shí)候,如果這個(gè)結(jié)點(diǎn)的mul值大于1,則只會(huì)將這個(gè)結(jié)點(diǎn)的mul值減1,而不是真正刪除。
            【mul域的作用】
            mul域最主要的作用就是解決值相同的結(jié)點(diǎn)對(duì)于某些操作的影響。另外,mul域的引入可以減少樹(shù)中結(jié)點(diǎn)的總個(gè)數(shù)(尤其是當(dāng)插入的結(jié)點(diǎn)數(shù)很多而結(jié)點(diǎn)的值的范圍不大的時(shí)候),從而降低時(shí)間復(fù)雜度(準(zhǔn)確來(lái)說(shuō)可以降低常數(shù))。
            【Splay Tree的進(jìn)階操作】
            <1>找非根結(jié)點(diǎn)的前趨和后繼。
            Splay Tree由于有伸展操作,可以將要求前趨或后繼的點(diǎn)伸展到根再求前趨或后繼。如果要不通過(guò)伸展操作找一個(gè)非根結(jié)點(diǎn)的前趨或后繼怎么辦?
            設(shè)這個(gè)結(jié)點(diǎn)為x。如果x有左子結(jié)點(diǎn),則x的前趨就是x的左子結(jié)點(diǎn)的右鏈上的最后一個(gè)結(jié)點(diǎn);如果x沒(méi)有左子結(jié)點(diǎn),則x的前趨就是從x開(kāi)始往上(往x的祖先)查找,直到找到第一個(gè)是其父結(jié)點(diǎn)的右子結(jié)點(diǎn)的結(jié)點(diǎn)為止,則這個(gè)結(jié)點(diǎn)的父結(jié)點(diǎn)就是x的前趨(或者說(shuō),就是不斷沿著x的父結(jié)點(diǎn)往上找,一開(kāi)始都是往右的,找到第一個(gè)往左的就行了)。后繼則正好相反。證明可以用中序遍歷。
            <2>找某個(gè)值v0的前趨和后繼(值為v0的結(jié)點(diǎn)在樹(shù)中不一定存在)。
            所謂v0的前趨和后繼,就是指在樹(shù)中值小于(也有的是不大于)v0的最大的結(jié)點(diǎn)的值,和樹(shù)中值大于(也有的是不小于)v0的最小的結(jié)點(diǎn)的值。在有了操作(1)【不通過(guò)伸展操作找非根結(jié)點(diǎn)的前趨和后繼】以后,這個(gè)操作變得極為容易:先進(jìn)行普通的查找操作(查找值為v0的結(jié)點(diǎn)),如果能找到,則剩下的步驟就和操作(1)一樣了;如果找不到,則必然是找到某個(gè)空結(jié)點(diǎn)(0)處,假設(shè)在這里插入一個(gè)值為v0的結(jié)點(diǎn)(只是假設(shè),不是真插入),則該結(jié)點(diǎn)顯然是沒(méi)有左子結(jié)點(diǎn)的,因此就是“從該結(jié)點(diǎn)開(kāi)始往上查找,直到找到第一個(gè)是其父結(jié)點(diǎn)的右子結(jié)點(diǎn)的結(jié)點(diǎn)為止,則這個(gè)結(jié)點(diǎn)的父結(jié)點(diǎn)就是該結(jié)點(diǎn)的前趨”,也就是v0的前趨;后繼類似。
            在查找過(guò)程中可以進(jìn)行一個(gè)常數(shù)優(yōu)化:設(shè)點(diǎn)i為查找過(guò)程中“右轉(zhuǎn)”的最后一個(gè)結(jié)點(diǎn)(即找到該結(jié)點(diǎn)后,接下來(lái)是該結(jié)點(diǎn)的右子結(jié)點(diǎn)),每次往右子結(jié)點(diǎn)轉(zhuǎn)的時(shí)候就更新i,則最后結(jié)點(diǎn)i就是值v0的前趨;后繼類似。
            <3>刪除所有值在某區(qū)間內(nèi)的結(jié)點(diǎn)(這個(gè)區(qū)間可以是開(kāi)區(qū)間、閉區(qū)間或半開(kāi)半閉區(qū)間)。
            以開(kāi)區(qū)間為例:刪除樹(shù)中所有值在(l, r)范圍內(nèi)的結(jié)點(diǎn)。先找到l的前趨P和r的后繼S(注意這里的前趨和后繼是包括“等于”的),然后將P伸展到根,S伸展到P的右子結(jié)點(diǎn)處,則S的左子樹(shù)中就是所有值在(l, r)范圍內(nèi)的結(jié)點(diǎn),直接刪掉這棵子樹(shù)即可(注意刪除后要更新S和P);若改為閉區(qū)間,則將前趨和后繼中的“等于”去掉(即必須是小于或大于);半開(kāi)半閉區(qū)間則一半按開(kāi)區(qū)間處理,一半按閉區(qū)間處理。問(wèn)題是,如果點(diǎn)P或點(diǎn)S不存在怎么辦。有兩種解決辦法,一是若P和S之一存在,則將其伸展到根,然后直接刪掉其左子樹(shù)或右子樹(shù)即可;若P和S都不存在,則樹(shù)為空,直接將root置為0即可;二是按照sequence的辦法,加入兩個(gè)邊界結(jié)點(diǎn),當(dāng)前趨或后繼不存在時(shí)就認(rèn)為是邊界結(jié)點(diǎn)。
            其實(shí)不光是刪除,在找到了這棵子樹(shù)后可以對(duì)它進(jìn)行一些整體操作,從而讓Splay Tree具有線段樹(shù)的功能(這個(gè)在下面的NOI2005 sequence那一題里面會(huì)總結(jié))。
            <4>插入一棵樹(shù)(當(dāng)然這棵樹(shù)也是Splay Tree,并且原樹(shù)中的結(jié)點(diǎn)值序列與新樹(shù)中的結(jié)點(diǎn)值序列不相交)。
            有時(shí)候,題目中會(huì)連續(xù)出現(xiàn)很多插入操作,中間沒(méi)有其它操作,此時(shí),與其一個(gè)一個(gè)插入,還不如將它們先建成一棵樹(shù)(建樹(shù)的方法在下一篇里總結(jié)),再整體插入。整體插入的方法:設(shè)L和R分別是新樹(shù)里值最小的結(jié)點(diǎn)和值最大的結(jié)點(diǎn),在原樹(shù)中找到L的前趨P和R的后繼S,將P伸展到根,S伸展到P的右子結(jié)點(diǎn)處,由于原樹(shù)中的結(jié)點(diǎn)值序列與新樹(shù)中的結(jié)點(diǎn)值序列不相交(也就是原樹(shù)中的結(jié)點(diǎn)值要么小于L的值,要么大于R的值),因此S無(wú)左子結(jié)點(diǎn),此時(shí)將新樹(shù)作為S的左子樹(shù)即可。

            posted @ 2011-06-21 11:31 Mato_No1 閱讀(761) | 評(píng)論 (0)編輯 收藏

                 摘要: 今天真是有紀(jì)念意義啊……以前試著捉了N遍的cashier今天竟然AC了,本沙茶終于掌握了平衡樹(shù)?。。 維play Tree及其實(shí)現(xiàn)】<1>結(jié)點(diǎn)記錄的信息:一般情況下Splay Tree是用線性存儲(chǔ)器(結(jié)構(gòu)數(shù)組)來(lái)存儲(chǔ)的,可以避免在Linux下的指針異常問(wèn)題。這樣對(duì)于某個(gè)結(jié)點(diǎn),至少要記錄以下的域:值(又叫關(guān)鍵字)、左子結(jié)點(diǎn)的下標(biāo)、右子結(jié)點(diǎn)的下標(biāo)、父結(jié)點(diǎn)下標(biāo)、子樹(shù)大...  閱讀全文

            posted @ 2011-06-18 21:21 Mato_No1 閱讀(1077) | 評(píng)論 (0)編輯 收藏

            給出一個(gè)帶邊權(quán)的無(wú)向圖G,設(shè)其最小生成樹(shù)為T,求出圖G的與T不完全相同的邊權(quán)和最小的生成樹(shù)(即G的次小生成樹(shù))。一個(gè)無(wú)向圖的兩棵生成樹(shù)不完全相同,當(dāng)且僅當(dāng)這兩棵樹(shù)中至少有一條邊不同。注意,圖G可能不連通,可能有平行邊,但一定沒(méi)有自環(huán)(其實(shí)對(duì)于自環(huán)也很好處理:直接舍棄。因?yàn)樯蓸?shù)中不可能出現(xiàn)自環(huán))。
            【具體題目】URAL1416(注意,這一題的邊數(shù)M的范圍沒(méi)有給出,視為124750)
            【分析】
            定義生成樹(shù)T的一個(gè)可行變換(-E1, +E2):將T中的邊E1刪除后,再加入邊E2(滿足邊E2原來(lái)不在T中但在G中),若得到的仍然是圖G的一棵生成樹(shù),則該變換為可行變換,該可行變換的代價(jià)為(E2權(quán)值 - E1權(quán)值)。這樣,很容易證明,G的次小生成樹(shù)就是由G的最小生成樹(shù)經(jīng)過(guò)一個(gè)代價(jià)最小的可行變換得到。進(jìn)一步可以發(fā)現(xiàn),這個(gè)代價(jià)最小的可行變換中加入的邊E2的兩端點(diǎn)如果為V1和V2,那么E1一定是原來(lái)最小生成樹(shù)中從V1到V2的路徑上的權(quán)值最大的邊。

            這樣,對(duì)于本題就有兩種算法了:(以下的T全部指G的最小生成樹(shù))
            (1)Prim:
            設(shè)立數(shù)組F,F(xiàn)[x][y]表示T中從x到y(tǒng)路徑上的最大邊的權(quán)值。F數(shù)組可以在用Prim算法求最小生成樹(shù)的過(guò)程中得出。每次將邊(i, j)加入后(j是新加入樹(shù)的邊,i=c[j]),枚舉樹(shù)中原有的每個(gè)點(diǎn)k(包括i,但不包括j),則F[k][j]=max{F[k][i], (i, j)邊權(quán)值},又由于F數(shù)組是對(duì)稱的,可以得到F[j][k]=F[k][j]。然后千萬(wàn)記住將圖G中的邊(i, j)刪除(就是將鄰接矩陣中(i, j)邊權(quán)值改為∞)!因?yàn)門中的邊是不能被加入的。等T被求出后,所有的F值也求出了,然后,枚舉點(diǎn)i、j,若鄰接矩陣中邊(i, j)權(quán)值不是無(wú)窮大(這說(shuō)明i、j間存在不在T中的邊),則求出{(i, j)邊權(quán)值 - F[i][j]}的值,即為加入邊(i, j)的代價(jià),求最小的總代價(jià)即可。
            另外注意三種特殊情況:【1】圖G不連通,此時(shí)最小生成樹(shù)和次小生成樹(shù)均不存在。判定方法:在擴(kuò)展T的過(guò)程中找不到新的可以加入的邊;【2】圖G本身就是一棵樹(shù),此時(shí)最小生成樹(shù)存在(就是G本身)但次小生成樹(shù)不存在。判定方法:在成功求出T后,發(fā)現(xiàn)鄰接矩陣中的值全部是無(wú)窮大;【3】圖G存在平行邊。這種情況最麻煩,因?yàn)檫@時(shí)代價(jià)最小的可行變換(-E1, +E2)中,E1和E2可能是平行邊!因此,只有建立兩個(gè)鄰接矩陣,分別存儲(chǔ)每?jī)牲c(diǎn)間權(quán)值最小的邊和權(quán)值次小的邊的權(quán)值,然后,每當(dāng)一條新邊(i, j)加入時(shí),不是將鄰接矩陣中邊(i, j)權(quán)值改為無(wú)窮大,而是改為連接點(diǎn)i、j的權(quán)值次小的邊的權(quán)值。

            代碼:
            #include <iostream>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re2(i, l, r) for (int i=l; i<r; i++)
            const int MAXN = 7000, INF = ~0U >> 2;
            int n, s[MAXN][MAXN], s2[MAXN][MAXN], f[MAXN][MAXN], c[MAXN], v[MAXN], res1 = 0, res2 = 0;
            bool vst[MAXN];
            void init()
            {
                freopen(
            "mst.in""r", stdin);
                scanf(
            "%d"&n);
                re(i, n) re(j, n) s[i][j] 
            = s2[i][j] = INF;
                
            int m, a, b, len;
                scanf(
            "%d"&m);
                
            if (!m) {
                    
            if (n > 1) res1 = -INF; res2 = -INF;
                    
            return;
                }
                re(i, m) {
                    scanf(
            "%d%d%d"&a, &b, &len); a--; b--;
                    
            if (len < s[a][b]) {s2[a][b] = s2[b][a] = s[a][b]; s[a][b] = s[b][a] = len;} else if (len < s2[a][b]) s2[a][b] = s2[b][a] = len;
                }
                fclose(stdin);
            }
            void solve()
            {
                re(i, n) {f[i][i] 
            = c[i] = vst[i] = 0; v[i] = s[0][i];} vst[0= 1;
                
            int l0, l1 = INF, x, y, z;
                re2(i, 
            1, n) {
                    l0 
            = INF; re(j, n) if (!vst[j] && v[j] < l0) {l0 = v[j]; x = j; y = c[j];}
                    
            if (l0 == INF) {res1 = res2 = -INF; return;}
                    vst[x] 
            = 1; res1 += l0; s[x][y] = s[y][x] = INF; if (s2[x][y] < INF && s2[x][y] - l0 < l1) l1 = s2[x][y] - l0;
                    re(j, n) 
            if (!vst[j] && s[x][j] < v[j]) {v[j] = s[x][j]; c[j] = x;}
                    re(j, n) 
            if (vst[j] && j != x) f[j][x] = f[x][j] = max(f[j][y], l0);
                }
                re(i, n
            -1) re2(j, i+1, n) if (s[i][j] < INF) {
                    z 
            = s[i][j] - f[i][j];
                    
            if (z < l1) l1 = z;
                }
                
            if (l1 == INF) res2 = -INF; else res2 = res1 + l1;
            }
            void pri()
            {
                freopen(
            "mst.out""w", stdout);
                printf(
            "Cost: %d\nCost: %d\n", res1 == -INF ? -1 : res1, res2 == -INF ? -1 : res2);
                fclose(stdout);
            }
            int main()
            {
                init();
                
            if (!res2) solve();
                pri();
                
            return 0;
            }
            效率分析:Prim算法求次小生成樹(shù)的時(shí)空復(fù)雜度均為O(N2)。

            (2)Kruskal:
            Kruskal算法也可以用來(lái)求次小生成樹(shù)。在準(zhǔn)備加入一條新邊(a, b)(該邊加入后不會(huì)出現(xiàn)環(huán))時(shí),選擇原來(lái)a所在連通塊(設(shè)為S1)與b所在連通塊(設(shè)為S2)中,點(diǎn)的個(gè)數(shù)少的那個(gè)(如果隨便選一個(gè),最壞情況下可能每次都碰到點(diǎn)數(shù)多的那個(gè),時(shí)間復(fù)雜度可能增至O(NM)),找到該連通塊中的每個(gè)點(diǎn)i,并遍歷所有與i相關(guān)聯(lián)的邊,若發(fā)現(xiàn)某條邊的另一端點(diǎn)j在未選擇的那個(gè)連通塊中(也就是該邊(i, j)跨越了S1和S2)時(shí),就說(shuō)明最終在T中"刪除邊(a, b)并加入該邊"一定是一個(gè)可行變換,且由于加邊是按照權(quán)值遞增順序的,(a, b)也一定是T中從i到j(luò)路徑上權(quán)值最大的邊,故這個(gè)可行變換可能成為代價(jià)最小的可行變換,計(jì)算其代價(jià)為[(i, j)邊權(quán)值 - (a, b)邊權(quán)值],取最小代價(jià)即可。注意,在遍歷時(shí)需要排除一條邊,就是(a, b)本身(具體實(shí)現(xiàn)時(shí)由于用DL邊表,可以將邊(a, b)的編號(hào)代入)。另外還有一個(gè)難搞的地方:如何快速找出某連通塊內(nèi)的所有點(diǎn)?方法:由于使用并查集,連通塊是用樹(shù)的方式存儲(chǔ)的,可以直接建一棵樹(shù)(準(zhǔn)確來(lái)說(shuō)是一個(gè)森林),用“最左子結(jié)點(diǎn)+相鄰結(jié)點(diǎn)”表示,則找出樹(shù)根后遍歷這棵樹(shù)就行了,另外注意在合并連通塊時(shí)也要同時(shí)合并樹(shù)。
            對(duì)于三種特殊情況:【1】圖G不連通。判定方法:遍歷完所有的邊后,實(shí)際加入T的邊數(shù)小于(N-1);【2】圖G本身就是一棵樹(shù)。判定方法:找不到這樣的邊(i, j);【3】圖G存在平行邊。這個(gè)對(duì)于Kruskal來(lái)說(shuō)完全可以無(wú)視,因?yàn)镵ruskal中兩條邊只要編號(hào)不同就視為不同的邊。
            其實(shí)Kruskal算法求次小生成樹(shù)還有一個(gè)優(yōu)化:每次找到邊(i, j)后,一處理完這條邊就把它從圖中刪掉,因?yàn)楫?dāng)S1和S2合并后,(i, j)就永遠(yuǎn)不可能再是可行變換中的E2了。

            代碼:
            #include <iostream>
            #include 
            <stdlib.h>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re3(i, l, r) for (int i=l; i<=r; i++)
            const int MAXN = 7000, MAXM = 130000, INF = ~0U >> 2;
            struct edge {
                
            int a, b, len, pre, next;
            } ed[MAXM 
            + MAXM];
            struct edge2 {
                
            int a, b, len, No;
            } ed2[MAXM];
            int n, m = 0, m2, u[MAXN], ch[MAXN], nx[MAXN], q[MAXN], res1 = 0, res2 = INF;
            void init_d()
            {
                re(i, n) ed[i].a 
            = ed[i].pre = ed[i].next = i;
                
            if (n % 2) m = n + 1else m = n;
            }
            void add_edge(int a, int b, int l)
            {
                ed[m].a 
            = a; ed[m].b = b; ed[m].len = l; ed[m].pre = ed[a].pre; ed[m].next = a; ed[a].pre = m; ed[ed[m].pre].next = m++;
                ed[m].a 
            = b; ed[m].b = a; ed[m].len = l; ed[m].pre = ed[b].pre; ed[m].next = b; ed[b].pre = m; ed[ed[m].pre].next = m++;
            }
            void del_edge(int No)
            {
                ed[ed[No].pre].next 
            = ed[No].next; ed[ed[No].next].pre = ed[No].pre;
                ed[ed[No 
            ^ 1].pre].next = ed[No ^ 1].next; ed[ed[No ^ 1].next].pre = ed[No ^ 1].pre;
            }
            void init()
            {
                freopen(
            "mst.in""r", stdin);
                scanf(
            "%d%d"&n, &m2);
                
            if (!m2) {
                    
            if (n > 1) res1 = -INF;
                    res2 
            = -INF; return;
                }
                init_d();
                
            int a, b, len;
                re(i, m2) {
                    scanf(
            "%d%d%d"&a, &b, &len);
                    ed2[i].No 
            = m; add_edge(--a, --b, len);
                    ed2[i].a 
            = a; ed2[i].b = b; ed2[i].len = len;
                }
                fclose(stdin);
            }
            int cmp(const void *s1, const void *s2)
            {
                
            return ((edge2 *)s1)->len - ((edge2 *)s2)->len;
            }
            void prepare()
            {
                re(i, n) u[i] 
            = ch[i] = nx[i] = -1;
                qsort(ed2, m2, 
            16, cmp);
            }
            int find(int x)
            {
                
            int r = x, r0 = x, tmp;
                
            while (u[r] >= 0) r = u[r];
                
            while (u[r0] >= 0) {tmp = u[r0]; u[r0] = r; r0 = tmp;}
                
            return r;
            }
            void uni(int r1, int r2, int No, int l0)
            {
                q[
            0= r1;
                
            int j, k, l1, front, rear;
                
            for (front=0, rear=0; front<=rear; front++) {
                    j 
            = ch[q[front]];
                    
            while (j != -1) {
                        q[
            ++rear] = j;
                        j 
            = nx[j];
                    }
                }
                re3(i, 
            0, rear) {
                    j 
            = q[i];
                    
            for (int p=ed[j].next; p != j; p=ed[p].next) {
                        k 
            = ed[p].b;
                        
            if (p != No && find(k) == r2) {
                            l1 
            = ed[p].len - l0;
                            
            if (l1 < res2) res2 = l1;
                            del_edge(p);
                        }
                    }
                }
                u[r2] 
            += u[r1]; u[r1] = r2; nx[r1] = ch[r2]; ch[r2] = r1;
            }
            void solve()
            {
                
            int r1, r2, l0, num = 0;
                re(i, m2) {
                    r1 
            = find(ed2[i].a); r2 = find(ed2[i].b);
                    
            if (r1 != r2) {
                        l0 
            = ed2[i].len; res1 += l0; num++;
                        
            if (u[r1] >= u[r2]) uni(r1, r2, ed2[i].No, l0); else uni(r2, r1, ed2[i].No ^ 1, l0);
                    }
                }
                
            if (num < n - 1) {res1 = res2 = -INF; return;}
                
            if (res2 == INF) res2 = -INF; else res2 += res1;
            }
            void pri()
            {
                freopen(
            "mst.out""w", stdout);
                printf(
            "Cost: %d\nCost: %d\n", res1 == -INF ? -1 : res1, res2 == -INF ? -1 : res2);
                fclose(stdout);
            }
            int main()
            {
                init();
                
            if (!res1 && res2 == INF) {
                    prepare();
                    solve();
                }
                pri();
                
            return 0;
            }
            效率分析:可以證明,如果每次都選取點(diǎn)少的連通塊,Kruskal算法求次小生成樹(shù)的時(shí)間復(fù)雜度為O(M*(logN+logM)),空間復(fù)雜度為O(M)。
            總結(jié):顯然Prim適用于稠密圖,而Kruskal適用于稀疏圖。

            下面是對(duì)于一些數(shù)據(jù)的測(cè)試結(jié)果(數(shù)據(jù)說(shuō)明:第1~9個(gè)點(diǎn)和第15個(gè)點(diǎn)為稠密圖或一般圖,第10~14個(gè)點(diǎn)為稀疏圖)

            Prim:


            Kruskal(加入刪邊優(yōu)化):


            Kruskal(未加刪邊優(yōu)化):

            posted @ 2011-05-29 16:03 Mato_No1 閱讀(6789) | 評(píng)論 (5)編輯 收藏

            給出一個(gè)帶邊權(quán)連通無(wú)向圖,當(dāng)中指定一個(gè)點(diǎn)V0,要求這個(gè)圖的一個(gè)生成樹(shù),使得樹(shù)中V0點(diǎn)的度數(shù)(和V0點(diǎn)關(guān)聯(lián)的邊數(shù))剛好為一個(gè)指定值P,求滿足此條件的邊權(quán)和最小的生成樹(shù)。

            【算法】(某神犇的論文里有詳細(xì)證明,這里省略了囧……)
            設(shè)l[i]為連接圖中點(diǎn)V0與點(diǎn)i之間的邊的長(zhǎng)度(若有多條邊取權(quán)值最小的,若沒(méi)有邊則為無(wú)窮大)。
            (1)在圖中刪除點(diǎn)V0,新的圖不一定是連通圖,設(shè)其有B個(gè)連通塊;
            (2)若P<B則無(wú)解,否則轉(zhuǎn)下一步;
            (3)對(duì)這B個(gè)連通塊分別求最小生成樹(shù),得到一個(gè)生成森林。然后在圖中重新加入點(diǎn)V0,對(duì)每個(gè)連通塊,找出該連通塊內(nèi)l值最小的點(diǎn)V',添加邊(V0, V'),這樣就得到了一棵初始的生成樹(shù),或者說(shuō),這是V0點(diǎn)度數(shù)為B的最小的生成樹(shù);
            (4)然后就是不斷往里面加入與V0相關(guān)聯(lián)的邊。從V0點(diǎn)開(kāi)始,對(duì)整棵生成樹(shù)BFS遍歷,對(duì)每個(gè)點(diǎn)i,找到目前的生成樹(shù)中從V0到i的路徑上權(quán)值最大的邊,設(shè)E_len[i]為這條邊的權(quán)值,E_No[i]為這條邊的編號(hào)(由于本沙茶使用的是DL邊表,故記錄編號(hào)),這個(gè)東東的求法很容易,省略;
            (5)最后,枚舉除V0點(diǎn)外的每個(gè)點(diǎn),找到(E_len[i]-l[i])值最大的點(diǎn)i,然后在圖中刪除邊E_No[i],加入邊(V0, i),這樣得到的仍然是原圖的生成樹(shù),且V0的度數(shù)增加1;
            (6)重復(fù)以上過(guò)程(P-B)次即得結(jié)果。

            【實(shí)現(xiàn)注意】
            為了編程實(shí)現(xiàn)更加方便,注意以下幾點(diǎn):
            (1)一開(kāi)始不加入V0,而不是加入了再刪去(但各點(diǎn)l值要在輸入時(shí)求出);
            (2)一開(kāi)始不用先求出B的值,而是先求出最小生成森林(用Kruskal,不斷加邊,直到加到不能再加為止)和初始生成樹(shù),再遍歷初始生成樹(shù)得到B值;
            (3)由于涉及刪邊,一定要用DL邊表。

            【代碼】(PKU1639,注意這題是求V0度數(shù)不超過(guò)給定值P的最小生成樹(shù),簡(jiǎn)單改裝即可):
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <string>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re2(i, l, r) for (int i=l; i<r; i++)
            const int MAXN = 30, MAXM = 5000, MAXLEN = 20, INF = ~0U >> 2;
            struct edge {
                
            int a, b, len, pre, next;
            } ed[MAXM 
            + MAXM];
            struct edge0 {
                
            int a, b, len;
            } ed0[MAXM];
            int n, m, m0 = 0, P, B = 0, l[MAXN], u[MAXN], B_No[MAXN], q[MAXN], E_No[MAXN], E_len[MAXN], res = 0;
            string NAMELIST[MAXN];
            bool vst[MAXN];
            void add_edge0(int a, int b, int len)
            {
                ed0[m0].a 
            = a; ed0[m0].b = b; ed0[m0++].len = len;
            }
            void init_d()
            {
                re(i, n) ed[i].a 
            = ed[i].pre = ed[i].next = i;
                
            if (n % 2) m = n + 1else m = n;
            }
            void add_edge(int a, int b, int len)
            {
                ed[m].a 
            = a; ed[m].b = b; ed[m].len = len; ed[m].pre = ed[a].pre; ed[m].next = a; ed[a].pre = m; ed[ed[m].pre].next = m++;
                ed[m].a 
            = b; ed[m].b = a; ed[m].len = len; ed[m].pre = ed[b].pre; ed[m].next = b; ed[b].pre = m; ed[ed[m].pre].next = m++;
            }
            void del_edge(int No)
            {
                ed[ed[No].pre].next 
            = ed[No].next; ed[ed[No].next].pre = ed[No].pre;
                ed[ed[No 
            ^ 1].pre].next = ed[No ^ 1].next; ed[ed[No ^ 1].next].pre = ed[No ^ 1].pre;
            }
            void init()
            {
                n 
            = 1; NAMELIST[0= "Park"int m_;
                scanf(
            "%d"&m_); char ch = getchar(), s01[MAXLEN], s02[MAXLEN]; int No1, No2, l0, tmp;
                re(i, MAXN) l[i] 
            = INF;
                re(i, m_) {
                    scanf(
            "%s %s %d", s01, s02, &l0); ch = getchar();
                    No1 
            = -1; re(j, n) if (NAMELIST[j] == s01) {No1 = j; break;} if (No1 == -1) NAMELIST[No1 = n++= s01;
                    No2 
            = -1; re(j, n) if (NAMELIST[j] == s02) {No2 = j; break;} if (No2 == -1) NAMELIST[No2 = n++= s02;
                    
            if (No1 > No2) {tmp = No1; No1 = No2; No2 = tmp;}
                    
            if (No1) add_edge0(No1, No2, l0); else if (l0 < l[No2]) l[No2] = l0;
                }
                scanf(
            "%d"&P);
            }
            int cmp(const void *s1, const void *s2)
            {
                
            return ((edge0 *)s1)->len - ((edge0 *)s2)->len;
            }
            int find_root(int x)
            {
                
            int r = x, r0 = x, tmp;
                
            while (u[r] >= 0) r = u[r];
                
            while (u[r0] >= 0) {tmp = u[r0]; u[r0] = r; r0 = tmp;}
                
            return r;
            }
            void uni(int r1, int r2)
            {
                
            int sum = u[r1] + u[r2];
                
            if (u[r1] >= u[r2]) {u[r1] = r2; u[r2] = sum;} else {u[r2] = r1; u[r1] = sum;}
            }
            void prepare()
            {
                qsort(ed0, m0, 
            12, cmp);
                re2(i, 
            1, n) u[i] = -1; init_d();
                
            int x1, x2, l0, r1, r2;
                re(i, m0) {
                    x1 
            = ed0[i].a; x2 = ed0[i].b; l0 = ed0[i].len; r1 = find_root(x1); r2 = find_root(x2);
                    
            if (r1 != r2) {uni(r1, r2); add_edge(x1, x2, l0); res += l0;}
                }
                re2(i, 
            1, n) B_No[i] = -1;
                
            int Best, Best_No;
                re2(i, 
            1, n) if (B_No[i] == -1) {
                    B_No[i] 
            = B; Best = l[i]; Best_No = q[0= i;
                    
            int j, k;
                    
            for (int front=0, rear=0; front<=rear; front++) {
                        j 
            = q[front];
                        
            for (int p=ed[j].next; p != j; p=ed[p].next) {
                            k 
            = ed[p].b;
                            
            if (B_No[k] == -1) {
                                B_No[k] 
            = B; q[++rear] = k; if (l[k] < Best) {Best = l[k]; Best_No = k;}
                            }
                        }
                    }
                    add_edge(
            0, Best_No, Best); res += Best; B++;
                }
            }
            void solve()
            {
                vst[
            0= 1;
                re2(P0, B, P) {
                    re2(i, 
            1, n) {E_len[i] = INF; vst[i] = 0;} q[0= 0;
                    
            int i, j, l0;
                    
            for (int front=0, rear=0; front<=rear; front++) {
                        i 
            = q[front];
                        
            for (int p=ed[i].next; p != i; p=ed[p].next) {
                            j 
            = ed[p].b; l0 = ed[p].len;
                            
            if (!vst[j]) {
                                vst[j] 
            = 1; q[++rear] = j;
                                
            if (E_len[i] > l0) {E_len[j] = E_len[i]; E_No[j] = E_No[i];} else {E_len[j] = l0; E_No[j] = p;}
                            }
                        }
                    }
                    
            int Best = 0, Best_No, Best_v;
                    re2(i, 
            1, n) {
                        l0 
            = E_len[i] - l[i];
                        
            if (l0 > Best) {Best = l0; Best_No = E_No[i]; Best_v = i;};
                    }
                    
            if (Best) {del_edge(Best_No); add_edge(0, Best_v, l[Best_v]); res -= Best;} else break;
                }
            }
            void pri()
            {
                printf(
            "Total miles driven: %d\n", res);
            }
            int main()
            {
                init();
                prepare();
                solve();
                pri();
                
            return 0;
            }

            posted @ 2011-05-23 21:05 Mato_No1 閱讀(596) | 評(píng)論 (0)編輯 收藏

                 摘要: 【1】PKU1094思考難度不大,就是不斷在有向圖中加邊,求加了幾條邊以后,圖中出現(xiàn)一條連接N個(gè)點(diǎn)的簡(jiǎn)單路徑(就是一條長(zhǎng)度為(N-1)的鏈),或者圖中出現(xiàn)環(huán)。判定連接N個(gè)點(diǎn)的簡(jiǎn)單路徑的算法:拓?fù)渑判颍裘看侮?duì)列中有且只有一個(gè)入度為0的點(diǎn),則這樣的路徑存在,所排出的順序就是結(jié)果。注意兩點(diǎn):(1)如果在前面的若干條邊加入后,已經(jīng)找到了解,那么就輸出解,結(jié)束(不管后面的邊了,即使后面的邊加入后形成環(huán)也無(wú)...  閱讀全文

            posted @ 2011-05-22 11:09 Mato_No1 閱讀(431) | 評(píng)論 (0)編輯 收藏

            經(jīng)GYZ、CLJ神牛指點(diǎn),在N次Error后終于注冊(cè)成功了……

            Topcoder與NOI的規(guī)則區(qū)別:
            【1】Topcoder的代碼不是一個(gè)完整的代碼(連main()都米有),而只是一個(gè)類,類里面只有一個(gè)方法(相當(dāng)于main())。不用輸入、輸出,系統(tǒng)會(huì)將輸入數(shù)據(jù)直接傳遞到這個(gè)方法的參數(shù)里,在方法執(zhí)行完后將返回值直接傳遞到輸出里。類名、方法名、輸入?yún)?shù)類型、輸出結(jié)果類型是在原題中規(guī)定的(但參數(shù)名、輸出結(jié)果名可以自定義)。代碼中可以(也必須)使用C++ STL。
            【2】捉題的時(shí)候,題目描述下面的編碼區(qū)里可以直接編代碼,編好后點(diǎn)下面的Compile編譯,再點(diǎn)Test測(cè)試(可以測(cè)試樣例和自己的數(shù)據(jù)),測(cè)試完畢后,點(diǎn)Submit提交。所以,不必向其它OJ一樣在IDE里編好再Ctrl+ACV提交。
            其它的可以參照網(wǎng)上其他人寫的東東。

            本沙茶先在里面捉了幾題(全是水題,神犇不要鄙視),代碼:

            SRM 506 DIV1 250:
            #include <iostream>
            #include 
            <string>
            #include 
            <stdio.h>
            #include 
            <stdlib.h>
            #include 
            <string.h>
            #include 
            <math.h>
            #include 
            <time.h>
            #include 
            <iomanip>
            #include 
            <vector>
            #include 
            <list>
            #include 
            <map>
            #include 
            <set>
            #include 
            <deque>
            #include 
            <stack>
            #include 
            <bitset>
            #include 
            <algorithm>
            #include 
            <functional>
            #include 
            <numeric>
            #include 
            <utility>
            #include 
            <sstream>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re1(i, n) for (int i=1; i<=n; i++)
            #define re2(i, l, r) for (int i=l; i<r; i++)
            #define re3(i, l, r) for (int i=l; i<=r; i++)
            #define rre(i, n) for (int i=n-1; i>=0; i--)
            #define rre1(i, n) for (int i=n; i>0; i--)
            #define rre2(i, r, l) for (int i=r-1; i>=l; i--)
            #define rre3(i, r, l) for (int i=r; i>=l; i--)
            #define debug(x) cout << #x << " = " << x << endl;
            #define pb push_back
            #define re_t(i,x) for(typeof(x.begin()) i=x.begin();i!=x.end();i++)
            #define all(x) x.begin(),x.end()
            #define SORT(x) sort(all(x))
            #define MP make_pair
            typedef pair
            <int,int> ii;
            typedef vector
            <int> vi;
            typedef vi::iterator vit;
            typedef 
            set<int> si;
            typedef si::iterator sit;
            typedef map
            <int,int> mii;
            typedef mii::iterator mit;
            typedef 
            long long ll;
            typedef unsigned 
            long long ull;
            typedef unsigned 
            int uint;
            typedef istringstream ISS;
            typedef ostringstream OSS;
            const int MAXN = 175000, INF = ~0U >> 2;
            class MathContest {
                
            public:
                
            int countBlack(string s0, int p)
                {
                    
            string s = "";
                    
            bool a[MAXN];
                    re(i, p) s 
            += s0;
                    
            int n = s.length(), res = 0;
                    re(i, n) a[i] 
            = s[i] == 'B';
                    
            int i = 0, j = s.length() - 1;
                    
            bool reversed = 0, turned = 0, v;
                    
            while (i <= j) {
                        
            if (reversed) {
                            v 
            = a[j--];
                            
            if (turned) v = !v;
                        } 
            else {
                            v 
            = a[i++];
                            
            if (turned) v = !v;
                        }
                        
            if (v) {res++; turned = !turned;} else reversed = !reversed;
                    }
                    
            return res;            
                }
            };
            SRM 506 DIV2 250:
            #include <iostream>
            #include 
            <string>
            #include 
            <stdio.h>
            #include 
            <stdlib.h>
            #include 
            <string.h>
            #include 
            <math.h>
            #include 
            <time.h>
            #include 
            <iomanip>
            #include 
            <vector>
            #include 
            <list>
            #include 
            <map>
            #include 
            <set>
            #include 
            <deque>
            #include 
            <stack>
            #include 
            <bitset>
            #include 
            <algorithm>
            #include 
            <functional>
            #include 
            <numeric>
            #include 
            <utility>
            #include 
            <sstream>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re1(i, n) for (int i=1; i<=n; i++)
            #define re2(i, l, r) for (int i=l; i<r; i++)
            #define re3(i, l, r) for (int i=l; i<=r; i++)
            #define rre(i, n) for (int i=n-1; i>=0; i--)
            #define rre1(i, n) for (int i=n; i>0; i--)
            #define rre2(i, r, l) for (int i=r-1; i>=l; i--)
            #define rre3(i, r, l) for (int i=r; i>=l; i--)
            #define debug(x) cout << #x << " = " << x << endl;
            #define pb push_back
            #define re_t(i,x) for(typeof(x.begin()) i=x.begin();i!=x.end();i++)
            #define all(x) x.begin(),x.end()
            #define SORT(x) sort(all(x))
            #define MP make_pair
            typedef pair
            <int,int> ii;
            typedef vector
            <int> vi;
            typedef vi::iterator vit;
            typedef 
            set<int> si;
            typedef si::iterator sit;
            typedef map
            <int,int> mii;
            typedef mii::iterator mit;
            typedef 
            long long ll;
            typedef unsigned 
            long long ull;
            typedef unsigned 
            int uint;
            typedef istringstream ISS;
            typedef ostringstream OSS;
            const int MAXN = 10000, INF = ~0U >> 2;
            class SlimeXSlimeRancher2 {
                
            public:
                
            int train(vector <int> a)
                {
                    
            int n = a.size(), m = -INF, res = 0;
                    re(i, n) m 
            = max(m, a[i]);
                    re(i, n) res 
            += m - a[i];
                    
            return res;
                }
            };
            SRM 506 DIV2 500:
            #include <iostream>
            #include 
            <string>
            #include 
            <stdio.h>
            #include 
            <stdlib.h>
            #include 
            <string.h>
            #include 
            <math.h>
            #include 
            <time.h>
            #include 
            <iomanip>
            #include 
            <vector>
            #include 
            <list>
            #include 
            <map>
            #include 
            <set>
            #include 
            <deque>
            #include 
            <stack>
            #include 
            <bitset>
            #include 
            <algorithm>
            #include 
            <functional>
            #include 
            <numeric>
            #include 
            <utility>
            #include 
            <sstream>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            #define re1(i, n) for (int i=1; i<=n; i++)
            #define re2(i, l, r) for (int i=l; i<r; i++)
            #define re3(i, l, r) for (int i=l; i<=r; i++)
            #define rre(i, n) for (int i=n-1; i>=0; i--)
            #define rre1(i, n) for (int i=n; i>0; i--)
            #define rre2(i, r, l) for (int i=r-1; i>=l; i--)
            #define rre3(i, r, l) for (int i=r; i>=l; i--)
            #define debug(x) cout << #x << " = " << x << endl;
            #define pb push_back
            #define re_t(i,x) for(typeof(x.begin()) i=x.begin();i!=x.end();i++)
            #define all(x) x.begin(),x.end()
            #define SORT(x) sort(all(x))
            #define MP make_pair
            typedef pair
            <int,int> ii;
            typedef vector
            <int> vi;
            typedef vi::iterator vit;
            typedef 
            set<int> si;
            typedef si::iterator sit;
            typedef map
            <int,int> mii;
            typedef mii::iterator mit;
            typedef 
            long long ll;
            typedef unsigned 
            long long ull;
            typedef unsigned 
            int uint;
            typedef istringstream ISS;
            typedef ostringstream OSS;
            const int MAXN = 10000, INF = ~0U >> 2;
            class SlimeXSlimesCity {
                
            public:
                
            int merge(vector <int> a)
                {
                    
            int n = a.size();
                    SORT(a);
                    ll s 
            = 0, s1;
                    
            int res = 0;
                    
            bool ff;
                    re(i, n) {
                        s 
            += a[i]; s1 = s; ff = 1;
                        re2(j, i 
            + 1, n) {
                            
            if (s1 < a[j]) {ff = 0break;}
                            s1 
            += a[j];
                        }
                        res 
            += ff;
                    }
                    
            return res;    
                }
            };

            posted @ 2011-05-15 12:20 Mato_No1 閱讀(1364) | 評(píng)論 (6)編輯 收藏

            僅列出標(biāo)題
            共12頁(yè): First 4 5 6 7 8 9 10 11 12 
            国产精品久久久久久久久久影院| 久久亚洲精品国产亚洲老地址| 精品久久久久久无码专区不卡| 九九精品99久久久香蕉| 久久综合狠狠色综合伊人| 国产三级精品久久| 亚洲va国产va天堂va久久| 中文字幕亚洲综合久久| 99精品国产免费久久久久久下载| 国产精品久久毛片完整版| 色婷婷狠狠久久综合五月| 国产91色综合久久免费| 亚洲国产精品嫩草影院久久| 77777亚洲午夜久久多喷| 亚洲精品无码久久毛片| 9191精品国产免费久久| 亚洲精品无码久久久久久| 久久露脸国产精品| 久久99精品国产| 亚洲αv久久久噜噜噜噜噜| 色悠久久久久久久综合网| 精品国产VA久久久久久久冰 | 久久精品国产亚洲AV嫖农村妇女| 丰满少妇人妻久久久久久4| 国产亚洲精品美女久久久| 久久久久久久女国产乱让韩| 久久久黄片| 久久国产精品免费一区| yellow中文字幕久久网| 久久国产精品99久久久久久老狼| 亚洲午夜久久久久久久久电影网 | AV无码久久久久不卡网站下载| 久久人人超碰精品CAOPOREN| 久久se这里只有精品| 成人精品一区二区久久久| 国产福利电影一区二区三区久久老子无码午夜伦不 | 97视频久久久| 怡红院日本一道日本久久 | 久久国产精品成人免费| 狠狠色丁香婷婷久久综合不卡| 久久久免费精品re6|