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

            網(wǎng)絡(luò)流最讓人不能忍受的不是模板,而是建模!尤其是某些題目里面需要搞一段很長(zhǎng)的預(yù)處理才能開(kāi)始寫(xiě)網(wǎng)絡(luò)流……

            最大閉合子圖就是其中的一種。如果要求最大閉合子圖的有向圖里面有環(huán)就很囧了,因?yàn)樵谀承╊}目里(比如NOI2009的pvz),取點(diǎn)是有先后順序的,因此環(huán)中的點(diǎn)一個(gè)也取不了(有的題則不是這樣,子圖里的點(diǎn)可以一次全部取來(lái),這時(shí)對(duì)于環(huán)就有兩種方案了:要么全取,要么一個(gè)不取,此時(shí)不用管環(huán),直接進(jìn)入網(wǎng)絡(luò)流即可),不僅如此,根據(jù)閉合子圖的定義,如果一個(gè)點(diǎn)i可以到達(dá)一個(gè)點(diǎn)j(注,是“可以到達(dá)”點(diǎn)j,也就是從i到j(luò)有路徑),而點(diǎn)j屬于某個(gè)環(huán),那么點(diǎn)i也不能取,因此在預(yù)處理中需要把點(diǎn)i也刪掉。以下將屬于某個(gè)環(huán)中的點(diǎn)成為“環(huán)點(diǎn)”,將可以到達(dá)環(huán)點(diǎn)的點(diǎn)稱為“環(huán)限制點(diǎn)”,這兩種點(diǎn)在預(yù)處理中都要?jiǎng)h除。

            本沙茶以前用的一般方法是:先求圖的傳遞閉包,找出所有的環(huán)點(diǎn)(能夠到達(dá)自己的點(diǎn)),再?gòu)拿總€(gè)環(huán)點(diǎn)開(kāi)始進(jìn)行逆向遍歷(將原圖所有邊反向,再遍歷),找到所有的環(huán)限制點(diǎn)。該方法的時(shí)間復(fù)雜度高達(dá)O(N3),且寫(xiě)起來(lái)也爆麻煩。

            其實(shí),真正用于去環(huán)的最佳方法是拓?fù)渑判颍。。?br>
            首先將原圖的所有邊反向,然后進(jìn)行拓?fù)渑判颍斜闅v到的點(diǎn)是保留下來(lái)的點(diǎn),而沒(méi)有遍歷到的點(diǎn)就是環(huán)點(diǎn)或環(huán)限制點(diǎn),需要?jiǎng)h除。
            【證明:環(huán)點(diǎn)顯然是不可能被遍歷到的,而在反向后的新圖中,對(duì)于一個(gè)環(huán)限制點(diǎn)j,必然存在一個(gè)環(huán)點(diǎn)i能夠到達(dá)它,而i不能被遍歷到,故j也不能被遍歷到。除了這兩種點(diǎn)外,其它的點(diǎn)的所有前趨必然也都不是環(huán)點(diǎn)或環(huán)限制點(diǎn)(否則這些點(diǎn)就成了環(huán)限制點(diǎn)),因此只要入度為0(不存在前趨)的點(diǎn)能夠遍歷到,這些點(diǎn)也能夠遍歷到,而入度為0的點(diǎn)顯然能遍歷到,故這些點(diǎn)也能被遍歷到。證畢】
            由于求反向圖和拓?fù)渑判蚨伎梢栽贠(M)時(shí)間內(nèi)完成,整個(gè)去環(huán)過(guò)程的時(shí)間復(fù)雜度就是O(M)的。

            下面附上NOI2009 pvz代碼:(注意,本題的第9個(gè)點(diǎn)是一個(gè)超級(jí)大數(shù)據(jù),最后建出來(lái)的網(wǎng)絡(luò)的邊數(shù)將會(huì)達(dá)到300000,故MAXM取150000,另外,本題必須使用Dinic,SAP會(huì)超)
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <string.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++)
            #define re2(i, l, r) for (int i=l; i<r; i++)
            const int MAXN = 602, MAXM = 150000, INF = ~0U >> 2;
            struct link {
                
            int kk;
                link 
            *next;
            *ed[MAXN], *ed2[MAXN];
            struct edge {
                
            int a, b, f, next;
                edge () {}
                edge (
            int _a, int _b, int _f): a(_a), b(_b), f(_f), next(-1) {}
            } ed_[MAXM 
            + MAXM];
            int n, m = 0, s, t, sc[MAXN], hd[MAXN], tl[MAXN], st[MAXN], lev[MAXN], q[MAXN], hs[MAXN], pr[MAXN], ind[MAXN], now, res = 0;
            bool vst[MAXN];
            void init()
            {
                freopen(
            "pvz.in""r", stdin);
                
            int n0, m0, A[20][30], num = 1, x, y, z;
                scanf(
            "%d%d"&n0, &m0);
                re(i, n0) re(j, m0) A[i][j] 
            = num++;
                n 
            = n0 * m0 + 2; s = 0; t = n - 1; memset(ed, 0, n << 2); memset(ed2, 0, n << 2);
                re1(i, n 
            - 2) {
                    scanf(
            "%d%d"&sc[i], &num);
                    re(j, num) {
                        scanf(
            "%d%d"&x, &y); z = A[x][y];
                        link 
            *p1 = new link; p1->kk = i; p1->next = ed[z]; ed[z] = p1;
                        link 
            *p2 = new link; p2->kk = z; p2->next = ed2[i]; ed2[i] = p2; ind[z]++;
                    }
                }
                re(i, n0) re2(j, 
            1, m0) {
                    z 
            = A[i][j];
                    link 
            *p1 = new link; p1->kk = z; p1->next = ed[z - 1]; ed[z - 1= p1;
                    link 
            *p2 = new link; p2->kk = z - 1; p2->next = ed2[z]; ed2[z] = p2; ind[z - 1]++;
                }
                fclose(stdin);
            }
            inline 
            void add_edge(int a, int b, int f)
            {
                ed_[m] 
            = edge(a, b, f);
                
            if (hd[a] != -1) tl[a] = ed_[tl[a]].next = m++else hd[a] = tl[a] = m++;
                ed_[m] 
            = edge(b, a, 0);
                
            if (hd[b] != -1) tl[b] = ed_[tl[b]].next = m++else hd[b] = tl[b] = m++;
            }
            void prepare()
            {
                
            int front = 0, rear = -1;
                re1(i, n 
            - 2if (!ind[i]) {q[++rear] = i; vst[i] = 1;}
                
            int i, j;
                
            for (; front<=rear; front++) {
                    i 
            = q[front];
                    
            for (link *p=ed2[i]; p; p=p->next) {
                        j 
            = p->kk; ind[j]--;
                        
            if (!ind[j]) {vst[j] = 1; q[++rear] = j;}
                    }
                }
                memset(hd, 
            -1, n << 2); memset(tl, -1, n << 2);
                re1(i, n 
            - 2if (vst[i]) {
                    
            if (sc[i] > 0) {res += sc[i]; add_edge(s, i, sc[i]);}
                    
            if (sc[i] < 0) add_edge(i, t, -sc[i]);
                }
                re1(i, n 
            - 2if (vst[i]) for (link *p=ed[i]; p; p=p->next) {
                    j 
            = p->kk;
                    
            if (vst[j]) add_edge(i, j, INF);
                }
            }
            void aug()
            {
                
            int z = hs[t], i = t, p;
                
            while (i != s) {
                    hs[i] 
            -= z; p = pr[i]; ed_[p].f -= z; ed_[p ^ 1].f += z; i = ed_[p].a;
                    
            if (!ed_[p].f) now = i;
                }
                res 
            -= z;
            }
            bool dfs()
            {
                q[
            0= s; memset(vst, 0, n); vst[s] = 1; lev[s] = 0;
                
            int i, j, f0;
                
            for (int front=0, rear=0; front<=rear; front++) {
                    i 
            = q[front];
                    
            for (int p=hd[i]; p != -1; p=ed_[p].next) {
                        j 
            = ed_[p].b; f0 = ed_[p].f;
                        
            if (!vst[j] && f0) {vst[j] = 1; lev[j] = lev[i] + 1; q[++rear] = j;}
                    }
                }
                
            if (!vst[t]) return 0;
                now 
            = s; hs[s] = INF; memset(vst, 0, n);
                re(i, n) st[i] 
            = hd[i];
                
            bool ff;
                
            while (!vst[s]) {
                    
            if (now == t) aug();
                    ff 
            = 0;
                    
            for (int p=st[now]; p != -1; p=ed_[p].next) {
                        j 
            = ed_[p].b; f0 = ed_[p].f;
                        
            if (lev[now] + 1 == lev[j] && !vst[j] && f0) {
                            st[now] 
            = pr[j] = p; hs[j] = hs[now] <= f0 ? hs[now] : f0; now = j; ff = 1break;
                        }
                    }
                    
            if (!ff) {
                        vst[now] 
            = 1;
                        
            if (now != s) now = ed_[pr[now]].a;
                    }
                }
                
            return 1;
            }
            void solve()
            {
                
            while (dfs()) ;
            }
            void pri()
            {
                freopen(
            "pvz.out""w", stdout);
                printf(
            "%d\n", res);
                fclose(stdout);
            }
            int main()
            {
                init();
                prepare();
                solve();
                pri();
                
            return 0;
            }

            posted @ 2011-03-27 18:30 Mato_No1 閱讀(1511) | 評(píng)論 (3)編輯 收藏

            【這次市選我掛得很慘……前3題全部爆0(騙分都米騙到)……就這種爛成績(jī)還能進(jìn)市隊(duì),可見(jiàn)合肥人之沙茶……】
            最不該掛的是第一題。第一問(wèn)就是個(gè)裸的最大閉合子圖,關(guān)鍵就出在第二問(wèn)上,要求最大閉合子圖的最小容量。本沙茶后來(lái)才發(fā)現(xiàn)這竟然是PKU原題!(PKU2987),因?yàn)椋畲罅髑蟪鰜?lái)的最大閉合子圖一定是容量最小的!故第二問(wèn)只要在求出最大流后來(lái)一次遍歷,找到S可達(dá)的結(jié)點(diǎn)個(gè)數(shù)即可。

            詳細(xì)證明(轉(zhuǎn)網(wǎng)上某神犇的):
            ———————————————————————————————————————————————————最大權(quán)不可能是負(fù)數(shù),當(dāng)是0的情況,自然是一個(gè)點(diǎn)不選最優(yōu),下面探討忽略0的情況。

                     1:首先我假設(shè)有兩個(gè)閉合子圖都擁有相同的最大權(quán),并且沒(méi)有共同點(diǎn),很容易證明不會(huì)出現(xiàn)這種情況,因?yàn)槿绻覀儼褍蓚€(gè)閉合子圖都選擇上,那么最大權(quán)會(huì)變大。

                     2:仍然假設(shè)有兩個(gè)閉合子圖都擁有相同的最大權(quán),但是有共同點(diǎn),即重疊的意思。根據(jù)閉合圖的特點(diǎn),這些共同點(diǎn)不是隨意的,可以知道,只要有一個(gè)點(diǎn)相同,那么這個(gè)點(diǎn)的能到達(dá)的所有后續(xù)點(diǎn)都必定是共同點(diǎn)。所以會(huì)得出一個(gè)結(jié)論,兩個(gè)閉合子圖重疊,重疊的部分必然是1個(gè)或者多個(gè)不重疊閉合圖。


                然后我們考慮不重疊的部分,這部分的點(diǎn)權(quán)和可以證明一定是非負(fù)。我們可以假設(shè)非重疊部分的點(diǎn)權(quán)和是負(fù)數(shù),那么假如我們刪掉這部分,只選取重疊部分(因?yàn)橹丿B部分肯定是閉合圖),那么最大權(quán)也會(huì)變大,矛盾。所以非重疊部分點(diǎn)權(quán)和一定是非負(fù)數(shù)。
                下面繼續(xù)探討非重疊部分的性質(zhì)。上面的證明已經(jīng)得出他們的點(diǎn)權(quán)和一定是非負(fù)。下面先拋開(kāi)點(diǎn)權(quán)和等于0的情況,先討論正數(shù)的情況。
            假設(shè)兩個(gè)閉合子圖的非重疊部分都是正數(shù),那么把這兩個(gè)部分加起來(lái)重新構(gòu)成閉合圖,最大權(quán)必然會(huì)變大,與假設(shè)的兩個(gè)同為最大權(quán)的閉合圖矛盾。固可以證明非重疊部分的點(diǎn)權(quán)和肯定是0。

                探討到這部分,我們已經(jīng)可以初步得出一個(gè)結(jié)論,就是一個(gè)最大權(quán)閉合子圖的點(diǎn)數(shù)多少問(wèn)題只能受到一些0權(quán)和子圖(所有點(diǎn)權(quán)加起來(lái)等于0的子圖)的干擾。

                重點(diǎn)來(lái)到這些0權(quán)和子圖上。下面我們又來(lái)做一個(gè)假設(shè),就是假設(shè)我們求出了一個(gè)最大權(quán)閉合子圖,并且里面有包含到一些0權(quán)和子圖。而我們這時(shí)候需要做的就是找到那些可以刪去的0權(quán)和子圖,當(dāng)我們刪去了所有的這些子圖,那么點(diǎn)數(shù)就可以達(dá)到最小。

                關(guān)鍵來(lái)了。到底哪些0權(quán)和子圖是可以刪去的,哪些0權(quán)和子圖是不可以刪去的呢?

                根據(jù)閉合圖的性質(zhì),我們要?jiǎng)h除一個(gè)點(diǎn),那么必須把所有能到達(dá)這個(gè)點(diǎn)的點(diǎn)刪去。那么很清晰的看到要?jiǎng)h除一個(gè)子圖,必須保證在這個(gè)子圖外沒(méi)有任何一個(gè)點(diǎn)指向這個(gè)子圖。也就是說(shuō)這個(gè)子圖是封閉的,只有出邊,沒(méi)有入邊。



                最后一步,假如我們能證明在求解最大權(quán)閉合圖的過(guò)程中保證不會(huì)選上這些0權(quán)和子圖,那么這個(gè)證明就可以結(jié)束了。
            通過(guò)最大流求解最大權(quán)閉合子圖,我們把正點(diǎn)權(quán)和源點(diǎn)建邊,邊權(quán)為點(diǎn)權(quán)值,負(fù)點(diǎn)權(quán)和匯點(diǎn)建邊,邊權(quán)為點(diǎn)權(quán)絕對(duì)值。仔細(xì)分析求解最大流的過(guò)程,會(huì)發(fā)現(xiàn)一些東西。

                由于那些可選可不選的0權(quán)和子圖是封閉的,沒(méi)有入邊的,那么在求解最大流的過(guò)程中,不可能有外部流補(bǔ)充,所以這些0權(quán)和子圖的每個(gè)點(diǎn)與源或者匯的邊都是滿流的。





                最后求最大權(quán)閉合子圖的點(diǎn),方法是從源點(diǎn)開(kāi)始通過(guò)非滿流邊搜索一個(gè)聯(lián)通圖,圖內(nèi)的點(diǎn)(除了源點(diǎn))就是求出來(lái)的最大權(quán)閉合子圖的點(diǎn)。而上面證明到0權(quán)和子圖的點(diǎn)和源匯都是滿流,并且沒(méi)有入邊,出邊也沒(méi)有流,那么肯定無(wú)法從源點(diǎn)搜索到。那么在求解的過(guò)程中這些0權(quán)和子圖的點(diǎn)是肯定沒(méi)有選上的。

               就此證畢。
               結(jié)論:通過(guò)最大流求解最大權(quán)閉合子圖的問(wèn)題,求出來(lái)的閉合子圖點(diǎn)數(shù)也是最少的。

            ———————————————————————————————————————————————————
            代碼:
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <string.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 = 10002, MAXM = 120000, INF = ~0U >> 2;
            struct edge {
                
            int a, b, f, next;
                edge () {}
                edge (
            int _a, int _b, int _f) : a(_a), b(_b), f(_f), next(-1) {}
            } ed[MAXM 
            + MAXM];
            int n, m = 0, s, t, hd[MAXN], tl[MAXN], st[MAXN], lev[MAXN], pr[MAXN], hs[MAXN], q[MAXN], now, res = 0, res_num = 0;
            bool vst[MAXN];
            void add_edge(int a, int b, int f)
            {
                ed[m] 
            = edge(a, b, f);
                
            if (hd[a] != -1) tl[a] = ed[tl[a]].next = m++else hd[a] = tl[a] = m++;
                ed[m] 
            = edge(b, a, 0);
                
            if (hd[b] != -1) tl[b] = ed[tl[b]].next = m++else hd[b] = tl[b] = m++;
            }
            void init()
            {
                freopen(
            "profit.in""r", stdin);
                
            int n0, m0, a0, b0, x;
                scanf(
            "%d%d"&n0, &m0);
                n 
            = n0 + 2; s = 0; t = n - 1;
                memset(hd, 
            -1, n << 2); memset(tl, -1, n << 2);
                re1(i, n0) {
                    cin 
            >> x;
                    
            if (x > 0) {add_edge(s, i, x); res += x;}
                    
            if (x < 0) add_edge(i, t, -x);
                }
                re1(i, m0) {
                    scanf(
            "%d%d"&a0, &b0);
                    add_edge(a0, b0, INF);
                }
                fclose(stdin);
            }
            void aug()
            {
                
            int z = hs[t], i = t, p;
                
            while (i != s) {
                    hs[i] 
            -= z; p = pr[i]; ed[p].f -= z; ed[p ^ 1].f += z; i = ed[p].a;
                    
            if (!ed[p].f) now = i;
                }
                res 
            -= z;
            }
            bool dfs()
            {
                q[
            0= s; memset(vst, 0, n); vst[s] = 1; lev[s] = 0;
                
            int i, j, f0;
                
            for (int front=0, rear=0; front<=rear; front++) {
                    i 
            = q[front];
                    
            for (int p=hd[i]; p != -1; p=ed[p].next) {
                        j 
            = ed[p].b; f0 = ed[p].f;
                        
            if (!vst[j] && f0) {vst[j] = 1; lev[j] = lev[i] + 1; q[++rear] = j;}
                    }
                }
                
            if (!vst[t]) return 0;
                now 
            = s; memset(vst, 0, n); hs[s] = INF;
                re(i, n) st[i] 
            = hd[i];
                
            bool ff;
                
            while (!vst[s]) {
                    
            if (now == t) aug();
                    ff 
            = 0;
                    
            for (int p=st[now]; p != -1; p=ed[p].next) {
                        j 
            = ed[p].b; f0 = ed[p].f;
                        
            if (lev[now] + 1 == lev[j] && !vst[j] && f0) {st[now] = pr[j] = p; hs[j] = hs[now] <= f0 ? hs[now] : f0; now = j; ff = 1break;}
                    }
                    
            if (!ff) {
                        vst[now] 
            = 1;
                        
            if (now != s) now = ed[pr[now]].a;
                    }
                }
                
            return 1;
            }
            void solve()
            {
                
            while (dfs()) ;
                q[
            0= s; memset(vst, 0, n); vst[s] = 1;
                
            int i, j, f0;
                
            for (int front=0, rear=0; front<=rear; front++) {
                    i 
            = q[front];
                    
            for (int p=hd[i]; p != -1; p=ed[p].next) {
                        j 
            = ed[p].b; f0 = ed[p].f;
                        
            if (!vst[j] && f0) {vst[j] = 1; res_num++; q[++rear] = j;}
                    }
                }
            }
            void pri()
            {
                freopen(
            "profit.out""w", stdout);
                printf(
            "%d\n%d\n", res, res_num);
                fclose(stdout);
            }
            int main()
            {
                init();
                solve();
                pri();
                
            return 0;
            }

            posted @ 2011-03-27 17:57 Mato_No1 閱讀(892) | 評(píng)論 (0)編輯 收藏

            歐拉環(huán):圖中經(jīng)過(guò)每條邊一次且僅一次的環(huán);
            歐拉路徑:圖中經(jīng)過(guò)每條邊一次且僅一次的路徑;
            歐拉圖:有至少一個(gè)歐拉環(huán)的圖;
            半歐拉圖:沒(méi)有歐拉環(huán),但有至少一條歐拉路徑的圖。

            【無(wú)向圖】
            一個(gè)無(wú)向圖是歐拉圖當(dāng)且僅當(dāng)該圖是連通的(注意,不考慮圖中度為0的點(diǎn),因?yàn)樗鼈兊拇嬖趯?duì)于圖中是否存在歐拉環(huán)、歐拉路徑?jīng)]有影響)且所有點(diǎn)的度數(shù)都是偶數(shù);一個(gè)無(wú)向圖是半歐拉圖當(dāng)且僅當(dāng)該圖是連通的且有且只有2個(gè)點(diǎn)的度數(shù)是奇數(shù)(此時(shí)這兩個(gè)點(diǎn)只能作為歐拉路徑的起點(diǎn)和終點(diǎn));

            證明:因?yàn)槿我庖粋€(gè)點(diǎn),歐拉環(huán)(或歐拉路徑)從它這里進(jìn)去多少次就要出來(lái)多少次,故(進(jìn)去的次數(shù)+出來(lái)的次數(shù))為偶數(shù),又因?yàn)?進(jìn)去的次數(shù)+出來(lái)的次數(shù))=該點(diǎn)的度數(shù)(根據(jù)定義),所以該點(diǎn)的度數(shù)為偶數(shù)。

            【有向圖】
            一個(gè)有向圖是歐拉圖當(dāng)且僅當(dāng)該圖的基圖(將所有有向邊變?yōu)闊o(wú)向邊后形成的無(wú)向圖,這里同樣不考慮度數(shù)為0的點(diǎn))是連通的且所有點(diǎn)的入度等于出度;一個(gè)有向圖是半歐拉圖當(dāng)且僅當(dāng)該圖的基圖是連通的且有且只有一個(gè)點(diǎn)的入度比出度少1(作為歐拉路徑的起點(diǎn)),有且只有一個(gè)點(diǎn)的入度比出度多1(作為終點(diǎn)),其余點(diǎn)的入度等于出度。

            證明:與無(wú)向圖證明類似,一個(gè)點(diǎn)進(jìn)去多少次就要出來(lái)多少次。

            【無(wú)向圖、有向圖中歐拉環(huán)的求法】
            與二分圖匹配算法類似,是一個(gè)深度優(yōu)先遍歷的過(guò)程,時(shí)間復(fù)雜度O(M)(因?yàn)橐粭l邊最多被訪問(wèn)一次)。核心代碼(邊是用邊表存儲(chǔ)的而不是鄰接鏈表,因?yàn)闊o(wú)向圖中需要對(duì)其逆向的邊進(jìn)行處理,在有向圖中,可以用鄰接鏈表存儲(chǔ)邊):
            void dfs(int x)
            {
                
            int y;
                
            for (int p=hd[x]; p != -1; p=ed[p].next) if (!ed[p].vst) {
                    y 
            = ed[p].b;
                    ed[p].vst 
            = 1;
                    ed[p 
            ^ 1].vst = 1;     //如果是有向圖則不要這句
                    dfs(y);
                    res[v
            --= y + 1;
                }
            }
            要注意的是在res中寫(xiě)入是逆序的,所以初始的v應(yīng)設(shè)成(邊數(shù)-1)。
            但是有一個(gè)問(wèn)題是,這是遞歸實(shí)現(xiàn)的,當(dāng)點(diǎn)數(shù)過(guò)多時(shí)有爆棧危險(xiǎn),所以最好使用非遞歸:
            void dfs()
            {
                
            int x = 0, y, tp = 1; stk[0= 0;
                re(i, n) now[i] 
            = hd[i];
                
            bool ff;
                
            while (tp) {
                    ff 
            = 0;
                    
            for (int p=now[x]; p != -1; p=ed[p].next) if (!ed[p].vst) {
                        y 
            = ed[p].b;
                        ed[p].vst 
            = 1;
                        ed[p 
            ^ 1].vst = 1;     //如果是有向圖則不要這句
                        now[x] = p; stk[tp++= y; x = y; ff = 1break;
                    }
                    
            if (!ff) {
                        res[v
            --= x + 1;
                        x 
            = stk[--tp - 1];
                    }
                }
            }
            當(dāng)原圖是歐拉圖時(shí),一定可以求出歐拉回路。當(dāng)原圖是半歐拉圖時(shí),求歐拉路徑,只要找到起點(diǎn)i和終點(diǎn)j,添加邊<j, i>(或(j, i)),求歐拉環(huán),再在求出的歐拉環(huán)中刪除添加的新邊即可。

            不過(guò)最為BT的還不是這個(gè),而是接下來(lái)的——
            【混合圖】
            混合圖(既有有向邊又有無(wú)向邊的圖)中歐拉環(huán)、歐拉路徑的判定需要借助網(wǎng)絡(luò)流!

            (1)歐拉環(huán)的判定:
            一開(kāi)始當(dāng)然是判斷原圖的基圖是否連通,若不連通則一定不存在歐拉環(huán)或歐拉路徑(不考慮度數(shù)為0的點(diǎn))。

            其實(shí),難點(diǎn)在于圖中的無(wú)向邊,需要對(duì)所有的無(wú)向邊定向(指定一個(gè)方向,使之變?yōu)橛邢蜻叄拐麄€(gè)圖變成一個(gè)有向歐拉圖(或有向半歐拉圖)。若存在一個(gè)定向滿足此條件,則原圖是歐拉圖(或半歐拉圖)否則不是。關(guān)鍵就是如何定向?

            首先給原圖中的每條無(wú)向邊隨便指定一個(gè)方向(稱為初始定向),將原圖改為有向圖G',然后的任務(wù)就是改變G'中某些邊的方向(當(dāng)然是無(wú)向邊轉(zhuǎn)化來(lái)的,原混合圖中的有向邊不能動(dòng))使其滿足每個(gè)點(diǎn)的入度等于出度。
            設(shè)D[i]為G'中(點(diǎn)i的出度 - 點(diǎn)i的入度)。可以發(fā)現(xiàn),在改變G'中邊的方向的過(guò)程中,任何點(diǎn)的D值的奇偶性都不會(huì)發(fā)生改變(設(shè)將邊<i, j>改為<j, i>,則i入度加1出度減1,j入度減1出度加1,兩者之差加2或減2,奇偶性不變)!而最終要求的是每個(gè)點(diǎn)的入度等于出度,即每個(gè)點(diǎn)的D值都為0,是偶數(shù),故可得:若初始定向得到的G'中任意一個(gè)點(diǎn)的D值是奇數(shù),那么原圖中一定不存在歐拉環(huán)!

            若初始D值都是偶數(shù),則將G'改裝成網(wǎng)絡(luò):設(shè)立源點(diǎn)S和匯點(diǎn)T,對(duì)于每個(gè)D[i]>0的點(diǎn)i,連邊<S, i>,容量為D[i]/2;對(duì)于每個(gè)D[j]<0的點(diǎn)j,連邊<j, T>,容量為-D[j]/2;G'中的每條邊在網(wǎng)絡(luò)中仍保留,容量為1(表示該邊最多只能被改變方向一次)。求這個(gè)網(wǎng)絡(luò)的最大流,若S引出的所有邊均滿流,則原混合圖是歐拉圖,將網(wǎng)絡(luò)中所有流量為1的中間邊(就是不與S或T關(guān)聯(lián)的邊)在G'中改變方向,形成的新圖G''一定是有向歐拉圖;若S引出的邊中有的沒(méi)有滿流,則原混合圖不是歐拉圖。

            為什么能這樣建圖?
            考慮網(wǎng)絡(luò)中的一條增廣路徑S-->i-->...-->j-->T,將這條從i到j(luò)的路徑在G'中全部反向,則:i的入度加1出度減1,j的入度減1出度加1,路徑中其它點(diǎn)的入度出度均不變。而i是和S相連的,因此初始D[i]>0,即i的出度大于入度,故這樣反向之后D[i]減少2;同理,j是和T相連的,這樣反向之后D[j]增加2。因此,若最大流中邊<S, i>滿流(流量為初始D[i]/2),此時(shí)D[i]值就變成了0,也就是i的入度等于出度。因此只要使所有S引出的邊全部滿流,所有初始D值>0的點(diǎn)的D值將等于0,又因?yàn)閷⑦呑兿蚝笏悬c(diǎn)的D值之和不變,所有初始D值小于0的點(diǎn)的D值也將等于0,而初始D值等于0的D點(diǎn)既不與S相連也不與T相連,所以它們是網(wǎng)絡(luò)中的中間點(diǎn),而中間點(diǎn)的流入量等于流出量,故它們的入度和出度一直不變,即D值一直為0。因此,整個(gè)圖G'成為歐拉圖。

            (2)歐拉路徑的判定:
            首先可以想到的是枚舉歐拉路徑的起點(diǎn)i和終點(diǎn)j,然后在圖中添加邊<j, i>,再求圖中是否有歐拉回路即可。但是,該算法的時(shí)間復(fù)雜度達(dá)到了O(M * 最大流的時(shí)間),需要優(yōu)化。
            前面已經(jīng)說(shuō)過(guò),在將邊變向的過(guò)程中任何點(diǎn)的D值的奇偶性都不會(huì)改變,而一個(gè)有向圖有歐拉路徑的充要條件是基圖連通且有且只有一個(gè)點(diǎn)的入度比出度少1(作為歐拉路徑的起點(diǎn)),有且只有一個(gè)點(diǎn)的入度比出度多1(作為終點(diǎn)),其余點(diǎn)的入度等于出度。這就說(shuō)明,先把圖中的無(wú)向邊隨便定向,然后求每個(gè)點(diǎn)的D值,若有且只有兩個(gè)點(diǎn)的初始D值為奇數(shù),其余的點(diǎn)初始D值都為偶數(shù),則有可能存在歐拉路徑(否則不可能存在)。進(jìn)一步,檢查這兩個(gè)初始D值為奇數(shù)的點(diǎn),設(shè)為點(diǎn)i和點(diǎn)j,若有D[i]>0且D[j]<0,則i作起點(diǎn)j作終點(diǎn)(否則若D[i]與D[j]同號(hào)則不存在歐拉路徑),連邊<j, i>,求是否存在歐拉環(huán)即可(將求出的歐拉環(huán)中刪去邊<j, i>即可)。這樣只需求一次最大流。

            【典型例題】Sightseeing tour(PKU1637,ZJU1992)
            本題就是求混合圖的歐拉環(huán)問(wèn)題,題目中已經(jīng)說(shuō)明圖是連通的(Input的最后一句話),故不需判連通。
            (本沙茶一開(kāi)始把DFS中的l0 = aug中的"= aug"寫(xiě)漏了,TLE了N次)
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <string.h>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            const int MAXN = 2002, MAXM = 10000, INF = ~0U >> 2;
            struct edge {
                
            int a, b, f, next;
                edge () {}
                edge (
            int _a, int _b, int _f): a(_a), b(_b), f(_f), next(-1) {}
            } ed[MAXM 
            + MAXM];
            int n, m, s, t, D[MAXN], hd[MAXN], tl[MAXN], lb[MAXN], vl[MAXN], flow, lmt;
            bool res;
            int dfs(int i, int aug)
            {
                
            if (i == t) {flow += aug; return aug;}
                
            int l0 = aug, l1, j, f0;
                
            for (int p=hd[i]; p != -1; p=ed[p].next) {
                    j 
            = ed[p].b; f0 = ed[p].f;
                    
            if (lb[i] == lb[j] + 1 && f0) {
                        l1 
            = dfs(j, l0 <= f0 ? l0 : f0);
                        l0 
            -= l1; ed[p].f -= l1; ed[p ^ 1].f += l1;
                        
            if (lb[s] == n || !l0) return aug;
                    }
                }
                
            int minlb = n - 1;
                
            for (int p=hd[i]; p != -1; p=ed[p].next) if (ed[p].f) {
                    j 
            = ed[p].b;
                    
            if (lb[j] < minlb) minlb = lb[j];
                }
                
            if (--vl[lb[i]]) vl[lb[i] = minlb + 1]++else lb[s] = n;
                
            return aug - l0;
            }
            inline 
            void add_edge(int a, int b, int f)
            {
                ed[m] 
            = edge(a, b, f);
                
            if (hd[a] != -1) tl[a] = ed[tl[a]].next = m++else hd[a] = tl[a] = m++;
                ed[m] 
            = edge(b, a, 0);
                
            if (hd[b] != -1) tl[b] = ed[tl[b]].next = m++else hd[b] = tl[b] = m++;
            }
            void solve()
            {
                
            int tests;
                scanf(
            "%d"&tests);
                
            int n0, m0, a0, b0, f;
                re(testno, tests) {
                    scanf(
            "%d%d"&n0, &m0);
                    n 
            = n0 + 2; m = 0; s = 0; t = n - 1;
                    memset(D, 
            0, n0 << 2); memset(hd, -1, n << 2); memset(tl, -1, n << 2);
                    re(i, m0) {
                        scanf(
            "%d%d%d"&a0, &b0, &f);
                        D[a0 
            - 1]++; D[b0 - 1]--;
                        
            if (!f) add_edge(a0, b0, 1);
                    }
                    res 
            = 1; lmt = 0; flow = 0;
                    re(i, n0) {
                        
            if (D[i] % 2) {res = 0break;}
                        
            if (D[i] > 0) {add_edge(s, i + 1, D[i] >> 1); lmt += D[i] >> 1;}
                        
            if (D[i] < 0) add_edge(i + 1, t, -D[i] >> 1);
                    }
                    
            if (res) {
                        memset(lb, 
            0, n << 2); vl[0= n; memset(vl + 10, n << 2);
                        
            while (lb[s] < n) dfs(s, INF);
                        
            if (flow < lmt) res = 0;
                    }
                    puts(res 
            ? "possible" : "impossible");
                }
            }
            int main()
            {
                solve();
                
            return 0;
            }

            posted @ 2011-03-27 11:06 Mato_No1 閱讀(2445) | 評(píng)論 (3)編輯 收藏

            在二分圖最大匹配中,每個(gè)點(diǎn)(不管是X方點(diǎn)還是Y方點(diǎn))最多只能和一條匹配邊相關(guān)聯(lián),然而,我們經(jīng)常遇到這種問(wèn)題,即二分圖匹配中一個(gè)點(diǎn)可以和多條匹配邊相關(guān)聯(lián),但有上限,或者說(shuō),Li表示點(diǎn)i最多可以和多少條匹配邊相關(guān)聯(lián)。

            二分圖多重匹配分為二分圖多重最大匹配與二分圖多重最優(yōu)匹配兩種,分別可以用最大流與最大費(fèi)用最大流解決。

            (1)二分圖多重最大匹配:
            在原圖上建立源點(diǎn)S和匯點(diǎn)T,S向每個(gè)X方點(diǎn)連一條容量為該X方點(diǎn)L值的邊,每個(gè)Y方點(diǎn)向T連一條容量為該Y方點(diǎn)L值的邊,原來(lái)二分圖中各邊在新的網(wǎng)絡(luò)中仍存在,容量為1(若該邊可以使用多次則容量大于1),求該網(wǎng)絡(luò)的最大流,就是該二分圖多重最大匹配的值。

            (2)二分圖多重最優(yōu)匹配:
            在原圖上建立源點(diǎn)S和匯點(diǎn)T,S向每個(gè)X方點(diǎn)連一條容量為該X方點(diǎn)L值、費(fèi)用為0的邊,每個(gè)Y方點(diǎn)向T連一條容量為該Y方點(diǎn)L值、費(fèi)用為0的邊,原來(lái)二分圖中各邊在新的網(wǎng)絡(luò)中仍存在,容量為1(若該邊可以使用多次則容量大于1),費(fèi)用為該邊的權(quán)值。求該網(wǎng)絡(luò)的最大費(fèi)用最大流,就是該二分圖多重最優(yōu)匹配的值。

            例題:
            【1】POJ1698 Alice's Chance
            將電影作為X方點(diǎn),每一天作為Y方點(diǎn)(最多50周,每周7天,所以共設(shè)350個(gè)Y方點(diǎn)),若第i個(gè)電影可以在第j天搞就連邊(i, j)。每個(gè)X方點(diǎn)的L值為該電影總共要搞多少天,每個(gè)Y方點(diǎn)的L值為1(每天最多只能搞一個(gè)電影),然后求二分圖多重最大匹配,若能使所有從源點(diǎn)連向X方點(diǎn)的邊都滿流,則輸出Yes,否則輸出No。
            【2】POJ2112 Optimal Milking
            先預(yù)處理求出每?jī)蓚€(gè)點(diǎn)(包括擠奶點(diǎn)和牛)間的最短距離,然后將所有擠奶點(diǎn)作為X方點(diǎn)(L值為該擠奶點(diǎn)最多可以容納多少牛),所有牛作為Y方點(diǎn)(L值為1),Xi和Yj間邊的權(quán)值為這兩個(gè)點(diǎn)之間的最短距離(若這兩點(diǎn)間不存在路徑則此處無(wú)邊),然后問(wèn)題就變成了求一個(gè)多重匹配,使得每個(gè)Y方點(diǎn)都有匹配點(diǎn)且匹配邊中權(quán)值的最大值最小。
            可以枚舉最大邊權(quán)值S,然后,原圖中所有權(quán)值大于S的邊都要?jiǎng)h去。若此時(shí)圖中存在符合要求的多重匹配,則S合法否則S不合法。由于S的合法性是單調(diào)的,所以可以二分枚舉S。

            posted @ 2011-03-26 21:53 Mato_No1 閱讀(3243) | 評(píng)論 (2)編輯 收藏

            今天終于搞懂了RMQ問(wèn)題無(wú)比神犇的ST算法……

            【離線RMQ問(wèn)題】
            題意:對(duì)于一個(gè)序列A[1..N],共有M次提問(wèn),每次都是問(wèn)A[l..r](1<=l<=r<=N)中的最值,求出每次提問(wèn)的答案。

            (1)純暴力枚舉:O(NM);
            (2)樹(shù)狀數(shù)組:O(M(logN)^2)【見(jiàn)樹(shù)狀數(shù)組解決離線RMQ問(wèn)題
            (3)ST算法:O(MlogN)……
            ST算法是基于分塊DP的。
            設(shè)F[i][j]為A[i..(i+2^j-1)](共2^j個(gè)元素)中的最值(前提是不能越界,即i+2^j-1 <= N),顯然F可以通過(guò)DP的方式得到:
            F[i][j] = min||max{F[i][j-1], F[i+2^(j-1)][j-1]}
            邊界:F[i][0]=A[i]。
            DP求F的值的時(shí)間復(fù)雜度為O(NlogN)(一共只有NlogN個(gè)F值有意義);

            然后,對(duì)于求A[l..r]中的最值,只要將A[l..r]拆成若干連續(xù)的段,使得每段的長(zhǎng)度都是2的整數(shù)次冪就行了,比如A[3..28]可以拆成A[3..18]、A[19..26]、A[27..28]三段,長(zhǎng)度分別是16(2^4)、8(2^3)、2(2^1),所以min||max{A[3..28]} = min||max{F[3][4], F[19][3], F[27][1]}。
            關(guān)鍵是怎么拆?方法:求出(r-l+1)(即A[l..r]的長(zhǎng)度)的二進(jìn)制形式,然后從高位到低位依次遍歷,如果找到1位就加上目前的位對(duì)應(yīng)的冪,如(28-3+1)=(11010)2,所以依次找到F[3][4]、F[3+2^4][3]、F[3+2^4+2^3][1]。注意此時(shí)需要預(yù)先設(shè)一個(gè)數(shù)組B,B[2^i]=i,以方便找到某個(gè)取出的冪對(duì)應(yīng)的指數(shù)。
            顯然,最多只有l(wèi)ogN段,所以一次提問(wèn)的時(shí)間復(fù)雜度為O(logN)。

            其實(shí)還有一種方法,就是先求出log(r-l+1)的值(下取整),設(shè)為x,然后F[l][x]和F[r-2^x+1][x]中的較大/較小值就是A[l..r]中的最值。這樣,一次提問(wèn)的時(shí)間復(fù)雜度就降到了O(1)。問(wèn)題是,系統(tǒng)log函數(shù)灰常慢,也許算一次log函數(shù)值的時(shí)間已經(jīng)超過(guò)了logN,這樣顯然得不償失。所以仍然推薦上面的方法。

            【核心代碼(以求最小值為例,最大值類似)】
            分段法:
            int MIN(int l0, int r0)
            {
                
            int min = INF, h = l0, d0, b0;
                
            for (int d = r0 - l0 + 1; d; d -= d0) {
                    d0 
            = d & -d; b0 = B[d0];
                    
            if (F[h][b0] < min) min = F[h][b0];
                    h 
            += d0;
                }
                
            return min;
            }
            求log值法:
            int MIN(int l0, int r0)
            {
                
            int v = (int)floor(log2(r0 - l0 + 1.0)), s1 = F[l0][v], s2 = F[r0 - (1 << v) + 1][v];
                
            return s1 <= s2 ? s1 : s2;
            }
            求F數(shù)組值的預(yù)處理代碼(注意,如果采用求log值的方法就不需要B數(shù)組了):
            void prepare()
            {
                re(i, n) F[i][
            0= a[i];
                
            int x;
                re2(j, 
            1, MAXS) {
                    
            if ((1 << j) <= n) B[1 << j] = j;
                    x 
            = n - (1 << j) + 1;
                    re(i, x) F[i][j] 
            = min(F[i][j - 1], F[i + (1 << j - 1)][j - 1]);
                }
            }

            【后經(jīng)效率測(cè)試,發(fā)現(xiàn)當(dāng)N=M=100000的隨機(jī)數(shù)據(jù)中,兩種方法都可以在0.4s以內(nèi)得出正確結(jié)果,其中l(wèi)og值法比分段法略慢0.01s左右,相差不大,但考慮到“盡量少使用數(shù)學(xué)函數(shù)”的原則,仍推薦分段法】

            posted @ 2011-03-26 10:13 Mato_No1 閱讀(472) | 評(píng)論 (1)編輯 收藏

            依照CLJ神犇的指示,最近本沙茶決定開(kāi)始被數(shù)據(jù)結(jié)構(gòu)題虐……先找來(lái)了省內(nèi)的一道題(就是這道囧)……

            題目大意:求兩個(gè)長(zhǎng)度為5N的序列的最長(zhǎng)公共子序列長(zhǎng)度,在兩個(gè)序列中,整數(shù)1~N分別都出現(xiàn)5次。1<=N<=20000。

            【注:本沙茶一開(kāi)始用線段樹(shù)的,后來(lái)在看了CLJ神犇的標(biāo)程(Orz!!)之后終于明白了樹(shù)狀數(shù)組解法囧……】

            LCS問(wèn)題的樸素時(shí)間復(fù)雜度為O(NM)。對(duì)于本題顯然需要優(yōu)化。
            觀察LCS的轉(zhuǎn)移方程:
            F[i][j] = F[i-1][j-1]+1(當(dāng)A[i]==B[j]時(shí))
            F[i][j] = max{F[i-1][j], F[i][j-1]}(當(dāng)A[i]!=B[j]時(shí))

            可以將F用滾動(dòng)數(shù)組來(lái)表示,即設(shè)F'為上階段的F(即F[i-1]),則本階段的F(即F[i])可以由F'求得:
            F[j] = F'[j-1]+1(當(dāng)A[i]==B[j]時(shí))
            F[j] = max{F'[j], F[j-1]}(當(dāng)A[i]!=B[j]時(shí))

            進(jìn)一步,這個(gè)F'其實(shí)都不用記錄,只需在每一階段更新一遍F即可:
            F[j] = F[j-1]+1(當(dāng)A[i]==B[j]時(shí))
            F[j] = max{F[j], F[j-1]}(當(dāng)A[i]!=B[j]時(shí))
            不過(guò)需要逆序更新(保證F[j-1]是上一階段的而不是本階段的),這與01背包有點(diǎn)像。

            由題意可以發(fā)現(xiàn),A[i]==B[j]的出現(xiàn)次數(shù)極少,在每階段中只會(huì)出現(xiàn)5次!我們可以預(yù)先求出這5個(gè)地方的值,然后對(duì)于其它的F[j],其在本階段的值其實(shí)就是它前面的最大值(max{F[1..j-1]}),又因?yàn)槲覀冏詈笾恍柚繤[N'](N'=5N,即序列長(zhǎng)度)即可,故可設(shè)計(jì)出以下算法:
            一開(kāi)始F[1..N]均為0,然后將以下內(nèi)容執(zhí)行N'次,第i次:
            (1)求出B序列中與A[i]相等的5個(gè)元素的位置,設(shè)為S[1..5];
            (2)依次更新F[S[5..1]],每個(gè)都更新為它前面的最大值加1(很容易知道為神馬),其它的值暫時(shí)不管;

            N'次執(zhí)行完后,整個(gè)序列中的最大值就是F[N']的值。由于這個(gè)算法中出現(xiàn)的主要操作是改動(dòng)一個(gè)指定位置元素的值和找一個(gè)前綴區(qū)間中的最大值,因此可以采用樹(shù)狀數(shù)組,時(shí)間復(fù)雜度O(NlogN)(線段樹(shù)必TLE)。

            【總結(jié):在本題中使用了一種“推遲更新”的方法,即需要更新一個(gè)值時(shí),先暫時(shí)不理它,等到需要引用到它的時(shí)候再更新。這種方法最常見(jiàn)的應(yīng)用就是線段樹(shù)的結(jié)點(diǎn)標(biāo)記。不過(guò)要注意的是,如果該值的推遲更新會(huì)對(duì)它后面要更新的值帶來(lái)問(wèn)題(也就是,這些后更新的值需要引用該值的新值),就不能使用這種方法。在本題中,其它位置的值的改變只與這5個(gè)特殊的位置有關(guān),與其它因素?zé)o關(guān),故可以使用這種方法。】

            posted @ 2011-03-19 22:38 Mato_No1 閱讀(931) | 評(píng)論 (0)編輯 收藏

            樹(shù)狀數(shù)組與線段樹(shù)不同,它只能直接支持前綴區(qū)間([1..r])或后綴區(qū)間([l..N])上的操作,而對(duì)于一般區(qū)間([l..r])上的操作則需要通過(guò)兩步操作間接完成:先對(duì)[1..r]進(jìn)行操作再對(duì)[1..l-1]進(jìn)行反操作(如加c的反操作就是減c),對(duì)于加法操作這樣可反的操作是可以,而對(duì)于求最值這樣的不可反的操作(無(wú)法通過(guò)[1..r]的最值與[1..l-1]的最值得出[l..r]的最值),就沒(méi)有辦法了。其實(shí),用樹(shù)狀數(shù)組是可以解決離線RMQ問(wèn)題的,但時(shí)間復(fù)雜度不太理想(一次操作的理論時(shí)間復(fù)雜度達(dá)O((logN)^2))。

            方法是(這里C[i]表示i管轄的數(shù)組結(jié)點(diǎn)中的最值):設(shè)r'為目前的右端點(diǎn),一開(kāi)始r'=r。每次找到r'管轄的數(shù)組結(jié)點(diǎn)中最左邊的那個(gè)的下標(biāo)(即r' - (r' & (-r')) + 1),設(shè)為x。若x>=l,則將C[r']與目前的最值比較、更新,再將r'設(shè)為(x-1);若x<l,則調(diào)出A[r']的值與目前最值比較、更新,然后將r'減1。如此直至r'<l為止。

            本算法編程復(fù)雜度極低,但由于時(shí)間效率較低,難以適應(yīng)較大范圍數(shù)據(jù)(N, M>100000基本上就TLE了)

            posted @ 2011-03-19 21:59 Mato_No1 閱讀(1338) | 評(píng)論 (1)編輯 收藏

            樹(shù)狀數(shù)組在區(qū)間求和問(wèn)題上有大用,其三種復(fù)雜度都比線段樹(shù)要低很多……有關(guān)區(qū)間求和的問(wèn)題主要有以下三個(gè)模型(以下設(shè)A[1..N]為一個(gè)長(zhǎng)為N的序列,初始值為全0):

            (1)“改點(diǎn)求段”型,即對(duì)于序列A有以下操作:

            【1】修改操作:將A[x]的值加上c;

            【2】求和操作:求此時(shí)A[l..r]的和。

            這是最容易的模型,不需要任何輔助數(shù)組。樹(shù)狀數(shù)組中從x開(kāi)始不斷減lowbit(x)(即x&(-x))可以得到整個(gè)[1..x]的和,而從x開(kāi)始不斷加lowbit(x)則可以得到x的所有前趨。代碼:

            void ADD(int x, int c)
            {
                 
            for (int i=x; i<=n; i+=i&(-i)) a[i] += c;
            }
            int SUM(int x)
            {
                
            int s = 0;
                
            for (int i=x; i>0; i-=i&(-i)) s += a[i];
                
            return s;
            }

             

            操作【1】:ADD(x, c);

            操作【2】:SUM(r)-SUM(l-1)。

            (2)“改段求點(diǎn)”型,即對(duì)于序列A有以下操作:

            【1】修改操作:將A[l..r]之間的全部元素值加上c;

            【2】求和操作:求此時(shí)A[x]的值。

            這個(gè)模型中需要設(shè)置一個(gè)輔助數(shù)組B:B[i]表示A[1..i]到目前為止共被整體加了多少(或者可以說(shuō)成,到目前為止的所有ADD(i, c)操作中c的總和)。

            則可以發(fā)現(xiàn),對(duì)于之前的所有ADD(x, c)操作,當(dāng)且僅當(dāng)x>=i時(shí),該操作會(huì)對(duì)A[i]的值造成影響(將A[i]加上c),又由于初始A[i]=0,所以有A[i] = B[i..N]之和!而ADD(i, c)(將A[1..i]整體加上c),將B[i]加上c即可——只要對(duì)B數(shù)組進(jìn)行操作就行了。

            這樣就把該模型轉(zhuǎn)化成了“改點(diǎn)求段”型,只是有一點(diǎn)不同的是,SUM(x)不是求B[1..x]的和而是求B[x..N]的和,此時(shí)只需把ADD和SUM中的增減次序?qū)φ{(diào)即可(模型1中是ADD加SUM減,這里是ADD減SUM加)。代碼:
            void ADD(int x, int c)
            {
                 
            for (int i=x; i>0; i-=i&(-i)) b[i] += c;
            }
            int SUM(int x)
            {
                
            int s = 0;
                
            for (int i=x; i<=n; i+=i&(-i)) s += b[i];
                
            return s;
            }

            操作【1】:ADD(l-1, -c); ADD(r, c);

            操作【2】:SUM(x)。

            (3)“改段求段”型,即對(duì)于序列A有以下操作:

            【1】修改操作:將A[l..r]之間的全部元素值加上c;

            【2】求和操作:求此時(shí)A[l..r]的和。

            這是最復(fù)雜的模型,需要兩個(gè)輔助數(shù)組:B[i]表示A[1..i]到目前為止共被整體加了多少(和模型2中的一樣),C[i]表示A[1..i]到目前為止共被整體加了多少的總和(或者說(shuō),C[i]=B[i]*i)。

            對(duì)于ADD(x, c),只要將B[x]加上c,同時(shí)C[x]加上c*x即可(根據(jù)C[x]和B[x]間的關(guān)系可得);

            而ADD(x, c)操作是這樣影響A[1..i]的和的:若x<i,則會(huì)將A[1..i]的和加上x(chóng)*c,否則(x>=i)會(huì)將A[1..i]的和加上i*c。也就是,A[1..i]之和 = B[i..N]之和 * i + C[1..i-1]之和。
            這樣對(duì)于B和C兩個(gè)數(shù)組而言就變成了“改點(diǎn)求段”(不過(guò)B是求后綴和而C是求前綴和)。
            另外,該模型中需要特別注意越界問(wèn)題,即x=0時(shí)不能執(zhí)行SUM_B操作和ADD_C操作!代碼:

            void ADD_B(int x, int c)
            {
                 
            for (int i=x; i>0; i-=i&(-i)) B[i] += c;
            }
            void ADD_C(int x, int c)
            {
                 
            for (int i=x; i<=n; i+=i&(-i)) C[i] += x * c;
            }
            int SUM_B(int x)
            {
                
            int s = 0;
                
            for (int i=x; i<=n; i+=i&(-i)) s += B[i];
                
            return s;
            }
            int SUM_C(int x)
            {
                
            int s = 0;
                
            for (int i=x; i>0; i-=i&(-i)) s += C[i];
                
            return s;
            }
            inline 
            int SUM(int x)
            {
                
            if (x) return SUM_B(x) * x + SUM_C(x - 1); else return 0;
            }

            操作【1】:
            ADD_B(r, c); ADD_C(r, c);
            if (l > 1) {ADD_B(l - 1, -c); ADD_C(l - 1, -c);}

            操作【2】:SUM(r) - SUM(l - 1)。

            posted @ 2011-03-19 19:53 Mato_No1 閱讀(3687) | 評(píng)論 (1)編輯 收藏

            代碼1:SAP單路增廣(非遞歸);

            代碼2:SAP多路增廣(遞歸);

            代碼3:Dinic單路增廣(非遞歸);

            代碼4:Dinic多路增廣(遞歸);

            結(jié)果:

            代碼1:

            代碼2:

            代碼3:

             代碼4:

            結(jié)果:
            SAP加了多路增廣后,直接秒掉后2個(gè)點(diǎn);
            Dinic加了多路增廣后效率差不多,還更低了一點(diǎn)……

            (另外發(fā)現(xiàn),SAP的多路增廣不支持當(dāng)前弧優(yōu)化……這點(diǎn)和zkw費(fèi)用流有點(diǎn)像囧……不過(guò)效率影響不大……)

            posted @ 2011-03-19 17:55 Mato_No1 閱讀(1195) | 評(píng)論 (4)編輯 收藏

            總算找到一個(gè)相對(duì)安靜的地方了……好高欣啊……

            希望在這里,本沙茶不要再聽(tīng)到太多害人的Orz聲(當(dāng)然,只是別Orz我,神犇們本來(lái)就是給人Orz的囧)……

            現(xiàn)在沒(méi)神馬動(dòng)力……來(lái)這里……就有動(dòng)力了囧……


            posted @ 2011-03-19 17:48 Mato_No1| 編輯 收藏

            僅列出標(biāo)題
            共12頁(yè): First 4 5 6 7 8 9 10 11 12 
            91久久婷婷国产综合精品青草 | a级毛片无码兔费真人久久| AV色综合久久天堂AV色综合在| av无码久久久久不卡免费网站| 国产免费久久久久久无码| 亚洲国产成人乱码精品女人久久久不卡 | 国产成人久久激情91| 精品久久久久久久久久久久久久久 | 色8激情欧美成人久久综合电| 精品伊人久久大线蕉色首页| 久久国产亚洲精品无码| 久久久WWW免费人成精品| 色播久久人人爽人人爽人人片AV| 国产精品一区二区久久不卡| 久久亚洲精品无码播放| 无码人妻久久久一区二区三区| 伊人丁香狠狠色综合久久| 欧美日韩精品久久免费| 人人狠狠综合久久亚洲婷婷| 2019久久久高清456| 国产精品va久久久久久久| 亚洲国产精品高清久久久| 岛国搬运www久久| 国色天香久久久久久久小说| 狠狠综合久久综合中文88| 亚洲AV日韩AV天堂久久| 久久午夜福利电影| 久久这里只精品国产99热| 一本色道久久88精品综合 | 久久精品国产乱子伦| 丁香久久婷婷国产午夜视频| 久久久女人与动物群交毛片| 亚洲精品成人网久久久久久| 久久99久久99小草精品免视看| 久久综合亚洲色HEZYO社区| 国产午夜福利精品久久| 九九久久自然熟的香蕉图片| 久久亚洲AV无码精品色午夜| 国产三级精品久久| 91精品国产色综久久| 91精品国产高清久久久久久io |