• <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ò)流圖,搞完了一般圖,最后應(yīng)該是二分圖了(在本沙茶知道的圖的范圍內(nèi))
            二分圖的邊有些特別,邊<a, b>并不是表示從a到b的邊,而是從X方點(diǎn)a(簡(jiǎn)記為Xa)到Y(jié)方點(diǎn)b(簡(jiǎn)記為Yb)的邊。在二分圖中,邊其實(shí)是無(wú)向的,但由于大多數(shù)引用的時(shí)候都是X方點(diǎn)引用Y方點(diǎn),所以可以把邊看成有向的(如果實(shí)在要逆向引用的話,加一條逆向邊吧囧……)
            邊類型定義(和一般圖完全一致。這里是不帶權(quán)的,如果像KM算法里面有帶權(quán)邊,加一個(gè)len域即可):
            struct edge {
                
            int a, b, pre, next;
            } ed[MAXM];
            關(guān)鍵是在初始化表頭的時(shí)候,設(shè)圖中有NX個(gè)X方點(diǎn)和NY個(gè)Y方點(diǎn),則單點(diǎn)鏈表數(shù)其實(shí)只有NX。故只需弄NX個(gè)表頭即可:
            void init_d()
            {
                re(i, nx) ed[i].a 
            = ed[i].pre = ed[i].next = i;
                
            if (nx % 2) m = nx + 1else m = nx;
            }
            然后是加邊,和一般圖完全一致:
            void add_edge(int a, int b)
            {
                ed[m].a 
            = a; ed[m].b = b; ed[m].pre = ed[a].pre; ed[m].next = a; ed[a].pre = m; ed[ed[m].pre].next = m++;
            }
            差不多就搞完了。下面是用Dancing Link邊表實(shí)現(xiàn)的Hungary算法的深度優(yōu)先遍歷部分:
            bool dfs(int x)
            {
                vst[x] 
            = 1;
                
            int y, x1;
                
            for (int p=ed[x].next; p != x; p=ed[p].next) {
                    y 
            = ed[p].b; x1 = xz[y];
                    
            if (x1 == -1 || !vst[x1] && dfs(x1)) {
                        xz[y] 
            = x; return 1;
                    }
                }
                
            return 0;
            }
            這里xz[y]指的是y所匹配的X方點(diǎn)編號(hào)(若y是未蓋點(diǎn)則xz[y]=-1),vst[x]是X方點(diǎn)x是否被訪問(wèn)的標(biāo)志,在每次遍歷前,vst都要全部清零。

            【應(yīng)用實(shí)例】PKU1087
            題意很難懂,其實(shí)就是:房間里有N個(gè)插座,每個(gè)插座都有一個(gè)型號(hào)(沒有兩個(gè)插座的型號(hào)相同),現(xiàn)在要把M個(gè)用電器插到這N個(gè)插座里,每個(gè)用電器有一個(gè)默認(rèn)型號(hào),表示該用電器只能插到其默認(rèn)型號(hào)的插座里(有可能有的用電器的默認(rèn)型號(hào)不與房間里的任意一個(gè)插座對(duì)應(yīng),如樣例中的X型號(hào))。有K種適配器(每種適配器可以用無(wú)限次),每一種適配器都有兩個(gè)參數(shù)S1和S2,表示該種適配器使用一次可以將某個(gè)默認(rèn)型號(hào)為S1的用電器的默認(rèn)型號(hào)改為S2(同一個(gè)用電器可以被多次改變默認(rèn)型號(hào))。問(wèn)在使用了若干次適配器后,最少有多少個(gè)用電器插不到插座里?
            首先將每種型號(hào)作為一個(gè)點(diǎn),若某種適配器的兩個(gè)參數(shù)分別為S1和S2則連邊<S1, S2>,對(duì)這個(gè)有向圖求傳遞閉包。然后再建一個(gè)二分圖,X方點(diǎn)表示用電器,Y方點(diǎn)表示插座,若Xi的初始默認(rèn)型號(hào)在一開始建的有向圖中可以到達(dá)Yj的型號(hào)(用傳遞閉包直接得到),則連邊(Xi, Yj)。對(duì)這個(gè)二分圖求最大匹配,則(M - 最大匹配值)就是結(jié)果。
            代碼:
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <string.h>
            #include 
            <string>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            const int MAXN = 500, MAXM = 250500, MAXLEN = 50, INF = ~0U >> 2;
            struct edge {
                
            int a, b, pre, next;
            } ed[MAXM];
            int n, nx, ny, m, xt[MAXN], yt[MAXN], xz[MAXN], res = 0;
            bool f[MAXN][MAXN], vst[MAXN];
            string nm[MAXN];
            void init()
            {
                
            string s1, s2;
                
            char s01[MAXLEN], s02[MAXLEN], ch;
                scanf(
            "%d"&ny); ch = getchar();
                re(i, ny) {gets(s01); nm[i] 
            = s01; yt[i] = i;} n = ny;
                scanf(
            "%d"&nx); ch = getchar();
                re(i, nx) {
                    scanf(
            "%s %s", s01, s02); ch = getchar(); xt[i] = n;
                    re(j, n) 
            if (nm[j] == s02) {xt[i] = j; break;}
                    
            if (xt[i] == n) nm[n++= s02;
                }
                
            int z, t1, t2;
                scanf(
            "%d"&z); ch = getchar();
                re(i, z) {
                    scanf(
            "%s %s", s01, s02); ch = getchar();
                    t1 
            = n; re(j, n) if (nm[j] == s01) {t1 = j; break;}
                    
            if (t1 == n) nm[n++= s01;
                    t2 
            = n; re(j, n) if (nm[j] == s02) {t2 = j; break;}
                    
            if (t2 == n) nm[n++= s02;
                    f[t1][t2] 
            = 1;
                }
                re(i, n) f[i][i] 
            = 1;
            }
            void add_edge(int a, int b)
            {
                ed[m].a 
            = a; ed[m].b = b; ed[m].pre = ed[a].pre; ed[m].next = a; ed[a].pre = m; ed[ed[m].pre].next = m++;
            }
            void prepare()
            {
                re(k, n) re(i, n) re(j, n) f[i][j] 
            = f[i][j] || f[i][k] && f[k][j];
                re(i, nx) ed[i].a 
            = ed[i].pre = ed[i].next = i;
                
            if (nx % 2) m = nx + 1else m = nx;
                re(i, nx) {
                    
            int t0 = xt[i];
                    re(j, ny) 
            if (f[t0][j]) add_edge(i, j);
                }
            }
            bool dfs(int x)
            {
                vst[x] 
            = 1;
                
            int y, x1;
                
            for (int p=ed[x].next; p != x; p=ed[p].next) {
                    y 
            = ed[p].b; x1 = xz[y];
                    
            if (x1 == -1 || !vst[x1] && dfs(x1)) {
                        xz[y] 
            = x; return 1;
                    }
                }
                
            return 0;
            }
            void solve()
            {
                re(i, ny) xz[i] 
            = -1;
                re(i, nx) {
                    re(j, ny) vst[j] 
            = 0;
                    res 
            += dfs(i);
                }
            }
            void pri()
            {
                printf(
            "%d\n", nx - res);
            }
            int main()
            {
                init();
                prepare();
                solve();
                pri();
                
            return 0;
            }
            有關(guān)圖的Dancing Link邊表的內(nèi)容就到此為止了。這真是一種有大用的數(shù)據(jù)結(jié)構(gòu)。

            posted @ 2011-05-08 12:04 Mato_No1 閱讀(417) | 評(píng)論 (0)編輯 收藏

            之前本沙茶成功地在網(wǎng)絡(luò)流圖中搞出Dancing Link邊表,那么對(duì)于一般的圖,是否也能用Dancing Link邊表呢?答案是肯定的。

            邊類型(帶權(quán)的,不帶邊權(quán)的圖把len域去掉即可):
            struct edge {
                
            int a, b, len, pre, next;
            } ed[MAXM];
            初始化表頭:
            void init_d()
            {
                re(i, n) ed[i].a 
            = ed[i].pre = ed[i].next = i;
                
            if (n % 2) m = n + 1else m = n;
            }
            加邊(這里是加有向邊,如果加無(wú)向邊的話,再加一條邊<b, a, len>即可):
            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++;
            }
            在一般的圖中應(yīng)用Dancing Link邊表的優(yōu)勢(shì):【1】能夠有效處理刪邊刪點(diǎn)問(wèn)題;【2】在無(wú)向圖中,能夠很快找到一條邊對(duì)應(yīng)的逆向邊;【3】最大的優(yōu)勢(shì)是:如果要從某一條單點(diǎn)鏈表(其實(shí)整個(gè)邊表可以看成N個(gè)單點(diǎn)鏈表的并,N為圖中的點(diǎn)數(shù))的中間開始遍歷,或者逆向遍歷整個(gè)表的話,一般的鄰接鏈表幾乎不可能完成,一般的邊表也很麻煩,這種Dancing Link邊表則可以很快搞定。不過(guò)它也有缺點(diǎn):空間消耗比鄰接鏈表和一般邊表大一些(不過(guò)這個(gè)影響不大)。

            【應(yīng)用實(shí)例】PKU1062(PKU上少有的中文題)
            很水的最短路問(wèn)題。將每個(gè)物品當(dāng)成一個(gè)點(diǎn),若j可作為i的優(yōu)惠品則連邊<i, j>,邊權(quán)為優(yōu)惠價(jià)格,然后,枚舉等級(jí)限制(由于物品1是必須選的,因此設(shè)最大等級(jí)限制為lmt,物品1的等級(jí)為V,則可在[V-lmt, V]范圍內(nèi)枚舉最低等級(jí),最高等級(jí)就是(最低等級(jí)+lmt)),將所有不在等級(jí)限制內(nèi)的點(diǎn)全部刪除(其實(shí)不用真刪除,只要設(shè)一個(gè)del數(shù)組避開它們即可),求從結(jié)點(diǎn)1到各結(jié)點(diǎn)的最短路,則min(dist[i]+cs[i])(dist[i]表示1到i的最短路,cs[i]表示直接購(gòu)買物品i的價(jià)格)就是結(jié)果。
            代碼(2Y,一開始把solve里的g[j]弄成g[i]了囧……靜態(tài)查錯(cuò)V5啊……神犇不要鄙視):
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <queue>
            #include 
            <utility>
            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++)
            typedef pair 
            <intint> i_i;
            typedef priority_queue 
            <i_i, vector<i_i>, greater<i_i> > pqi_i;
            const int MAXN = 100, MAXM = 30000, INF = ~0U >> 2;
            struct edge {
                
            int a, b, len, pre, next;
            } ed[MAXM];
            int n, m, s, lmt, cs[MAXN], g[MAXN], dist[MAXN], res = INF;
            bool del[MAXN];
            pqi_i q;
            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++;
            }
            void init()
            {
                
            int b0, x, y;
                scanf(
            "%d%d"&lmt, &n);
                init_d();
                re(i, n) {
                    scanf(
            "%d%d%d"&cs[i], &g[i], &x);
                    re(j, x) {
                        scanf(
            "%d%d"&b0, &y);
                        add_edge(i, 
            --b0, y);
                    }
                }
            }
            void sol1()
            {
                re(i, n) 
            if (!del[i]) dist[i] = INF + 1; q.push(i_i(0, s));
                
            int i, j, d0, d1;
                
            while (!q.empty()) {
                    d0 
            = q.top().first; i = q.top().second; q.pop();
                    
            if (dist[i] == INF + 1) {
                        dist[i] 
            = d0;
                        
            for (int p=ed[i].next; p != i; p=ed[p].next) {
                            j 
            = ed[p].b;
                            
            if (!del[j]) {
                                d1 
            = d0 + ed[p].len; q.push(i_i(d1, j));
                            }
                        }
                    }
                }
                re(i, n) 
            if (!del[i]) {
                    d0 
            = cs[i] + dist[i];
                    
            if (d0 < res) res = d0;
                }
            }
            void solve()
            {
                
            int lf, rt; s = 0;
                re3(i, 
            0, lmt) {
                    lf 
            = g[s] - i; rt = lf + lmt;
                    re(j, n) del[j] 
            = g[j] < lf || g[j] > rt;
                    sol1();
                }
            }
            void pri()
            {
                printf(
            "%d\n", res);
            }
            int main()
            {
                init();
                solve();
                pri();
                
            return 0;
            }

            posted @ 2011-05-08 10:04 Mato_No1 閱讀(484) | 評(píng)論 (0)編輯 收藏

            考慮這樣一種網(wǎng)絡(luò)流問(wèn)題:需要對(duì)同一個(gè)圖求多次最大流。則在每次求最大流之前,需要將所有邊的容量全部恢復(fù)到初始值(求最大流的過(guò)程中,邊的容量f值被改變了)。不過(guò)這還不算最猥瑣的,有的時(shí)候,我們需要在每次求最大流之前都刪去圖中的一些點(diǎn)或一些邊,或者改變某些原有的邊的容量,特別是需要?jiǎng)h點(diǎn)或刪邊的情況爆難搞。因?yàn)?,一般的邊表中邊類型定義如下:
            struct edge {
                    
            int a, b, f, next;
            } ed[MAXM 
            + MAXM];
            表示這條邊起點(diǎn)為a,終點(diǎn)為b,容量為f,鄰接邊(就是下一條起點(diǎn)為a的邊)的編號(hào)為next。
            如果要求多次最大流,那么在每次求最大流之前就要把所有邊的容量恢復(fù)到初始值,解決方法是引入一個(gè)“初始容量”域fs,在這條邊剛剛被加入的時(shí)候?qū)⑺膄s值和f值都設(shè)為初始給它的容量,在恢復(fù)時(shí),只要將所有邊的f值恢復(fù)到fs即可。若要改變邊容量,則將該邊的fs值和f值都設(shè)為改變后的容量即可。

            下面來(lái)分析需要?jiǎng)h邊或刪點(diǎn)的情況。在這種情況下,如果采用只有next的單向鏈表,則刪除時(shí)next域極難處理,而且,在一般的邊表中,還設(shè)立了表頭hd,hd[a]表示邊表中起點(diǎn)為a的第一條邊的編號(hào)。因此,若刪除的邊<a, b, f>是起點(diǎn)為a的第一條邊,還會(huì)影響hd[a]的值,使情況變得更為復(fù)雜。因此,必須采用雙向鏈表!還記得Dancing Link么?邊表其實(shí)也可以搞成Dancing Link,方法如下:
            設(shè)圖中有N個(gè)點(diǎn),M條邊(注意,這M條邊只包括正向邊,不包括反向邊。由于每條正向邊<a, b, f>都對(duì)應(yīng)一條反向邊<b, a, 0>,因此邊表中邊的數(shù)目其實(shí)是M+M)。首先把邊表ed的0~N這(N+1)個(gè)位置(下標(biāo))空出來(lái),作表頭(表頭不是邊,因此在遍歷邊的時(shí)候不會(huì)遍歷到它們)。其中,ed[0]為總表頭,用于遍歷ed[1..N]中每個(gè)未被刪去的點(diǎn);ed[1..N]為單點(diǎn)表頭,ed[i]用來(lái)遍歷圖中所有以i為起點(diǎn)的邊(和DLX中的二維DL驚人相似)。然后,若N為偶數(shù),則空一個(gè)位置(也就是將ed[N+1]丟棄不用),這是因?yàn)槲覀冊(cè)谠鰪V過(guò)程中需要引用到一條邊對(duì)應(yīng)的逆向邊(正向邊對(duì)應(yīng)反向邊,反向邊對(duì)應(yīng)正向邊),一般認(rèn)為編號(hào)為p的邊對(duì)應(yīng)的逆向邊是p ^ 1,這樣,就要求圖中所有正向邊的編號(hào)都是偶數(shù),所有反向邊的編號(hào)都是奇數(shù)(否則會(huì)造成混亂)。因此當(dāng)N為偶數(shù)時(shí),(N+1)為奇數(shù),不能放置第一條正向邊,需要從ed[N+2]開始放置正向邊。若N為奇數(shù)則不用空位。
            接下來(lái)就是邊類型了。在這里,邊類型一共需要包括6個(gè)域:a, b, fs, f, pre, next,表示這條邊起點(diǎn)為a,終點(diǎn)為b,初始容量為fs,當(dāng)前容量為f,上一條起點(diǎn)為a的邊編號(hào)為pre,下一條起點(diǎn)為a的邊編號(hào)為next。注意,和DL一樣,整個(gè)鏈表是循環(huán)的,也就是我們認(rèn)為表中最后一條起點(diǎn)為a的邊的下一條鄰接邊編號(hào)就是a(表頭),同樣,a的上一條鄰接邊也就是這條邊。
            struct edge {
                
            int a, b, fs, f, pre, next;
            } ed[MAXM 
            + MAXM];
            接下來(lái)就是幾個(gè)重要過(guò)程了。
            (1)初始化表頭:
            void init_d()
            {
                re1(i, n) {ed[i].a 
            = i; ed[i].b = -1; ed[i].f = 0; ed[i].pre = ed[i].next = i;}
                
            if (n % 2) m = n + 1else m = n + 2;
            }
            這里n是圖中的點(diǎn)數(shù)(相當(dāng)于N),m是邊的編號(hào)指針(相當(dāng)于DLX中的nodes)
            (2)添加新邊:
            void add_edge(int a, int b, int f)
            {
                ed[m].a 
            = a; ed[m].b = b; ed[m].fs = ed[m].f = f; 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].fs = ed[m].f = 0; ed[m].pre = ed[b].pre; ed[m].next = b; ed[b].pre = m; ed[ed[m].pre].next = m++;
            }
            這個(gè)和DLX類似,不解釋了囧……

            最后進(jìn)入最核心的部分——到底如何處理刪邊或刪點(diǎn)?有了DL型邊表就爆好搞了:刪去一條邊,只要直接刪去該邊在DL中的位置即可:
            void deledge(int No)
            {
                ed[ed[No].pre].next 
            = ed[No].next; ed[ed[No].next].pre = ed[No].pre;
            }
            恢復(fù)一條已刪去的邊:
            void resuedge(int No)
            {
                ed[ed[No].pre].next 
            = ed[ed[No].next].pre = No;
            }
            需要?jiǎng)h點(diǎn)的情況類似,對(duì)單點(diǎn)表頭處理即可。

            【具體題目】PKU1815
            這題就是求有向圖的字典序最小的最小點(diǎn)割集問(wèn)題,方法是先求出最小點(diǎn)連通度(有關(guān)最小點(diǎn)連通度的求法見圖的連通度問(wèn)題的求法),然后按編號(hào)遞增順序枚舉每個(gè)點(diǎn),若刪去該點(diǎn)(其實(shí)是刪去建成的新圖中該點(diǎn)i'到該點(diǎn)附加點(diǎn)i''之間的邊)后圖的最小點(diǎn)連通度減小,則應(yīng)刪去該點(diǎn),否則不應(yīng)刪去該點(diǎn)。刪去后,繼續(xù)枚舉下一個(gè)點(diǎn),直到求出點(diǎn)割集為止。
            注意,本題只有刪邊,沒有刪點(diǎn),因此總表頭可以不需要,直接從ed[0]開始作單點(diǎn)表頭。此時(shí),關(guān)于是否空位就剛好反過(guò)來(lái)了:如果N是奇數(shù)就要空位,N是偶數(shù)不空位(不過(guò)這題里由于建出的網(wǎng)絡(luò)流圖中有2*N0個(gè)結(jié)點(diǎn),總是偶數(shù),可以不管,不過(guò)本沙茶還是管這個(gè)了)。

            代碼(神犇不要鄙視):
            #include <iostream>
            #include 
            <stdio.h>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            const int MAXN = 501, MAXM = 100000, INF = ~0U >> 2;
            struct edge {
                
            int a, b, fs, f, pre, next;
            } ed[MAXM 
            + MAXM];
            int n0, n, m = 0, s, t, start[MAXN], pr[MAXN], hs[MAXN], lev[MAXN], q[MAXN], now, flow, reslen = 0, res[MAXN];
            bool vst[MAXN];
            void init_d()
            {
                re(i, n) {ed[i].a 
            = i; ed[i].b = -1; ed[i].f = 0; ed[i].pre = ed[i].next = i;}
                
            if (n % 2) m = n + 1else m = n;
            }
            void add_edge(int a, int b, int f)
            {
                ed[m].a 
            = a; ed[m].b = b; ed[m].fs = ed[m].f = f; 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].fs = ed[m].f = 0; ed[m].pre = ed[b].pre; ed[m].next = b; ed[b].pre = m; ed[ed[m].pre].next = m++;
            }
            void init()
            {
                
            int x;
                scanf(
            "%d%d%d"&n0, &s, &t);
                n 
            = n0 << 1; s--; t += n0 - 1; init_d();
                re(i, n0) re(j, n0) {
                    scanf(
            "%d"&x);
                    
            if (i == j) {
                        
            if (i == s || i == t - n0) add_edge(i, i + n0, INF); else add_edge(i, i + n0, 1);
                    } 
            else if (x) add_edge(i + n0, j, INF);
                }
            }
            void aug()
            {
                
            int z = hs[t], i = t, p; flow += z;
                
            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;
                }
            }
            bool dfs()
            {
                re(i, n) vst[i] 
            = 0; vst[s] = 1; q[0= s; lev[s] = 0;
                
            int i, j, f0;
                
            for (int front=0, rear=0; front<=rear; front++) {
                    i 
            = q[front];
                    
            for (int p=ed[i].next; p != i; p=ed[p].next) if (ed[p].f) {
                        j 
            = ed[p].b;
                        
            if (!vst[j]) {vst[j] = 1; q[++rear] = j; lev[j] = lev[i] + 1;}
                    }
                }
                
            if (!vst[t]) return 0;
                now 
            = s; re(i, n) {start[i] = ed[i].next; vst[i] = 0;} hs[now] = INF;
                
            bool fd;
                
            while (!vst[s]) {
                    
            if (now == t) aug();
                    fd 
            = 0;
                    
            for (int p=start[now]; p != now; p=ed[p].next) {
                        j 
            = ed[p].b; f0 = ed[p].f;
                        
            if (lev[now] + 1 == lev[j] && !vst[j] && f0) {
                            fd 
            = 1; start[now] = pr[j] = p; hs[j] = hs[now] <= f0 ? hs[now] : f0; now = j; break;
                        }
                    }
                    
            if (!fd) {
                        vst[now] 
            = 1;
                        
            if (now != s) now = ed[pr[now]].a;
                    }
                }
                
            return 1;
            }
            void deledge(int No)
            {
                ed[ed[No].pre].next 
            = ed[No].next; ed[ed[No].next].pre = ed[No].pre;
            }
            void resuedge(int No)
            {
                ed[ed[No].pre].next 
            = ed[ed[No].next].pre = No;
            }
            void resu_all()
            {
                re(i, n) 
            for (int p=ed[i].next; p != i; p=ed[p].next) ed[p].f = ed[p].fs;
            }
            void solve()
            {
                flow 
            = 0while (dfs()) ; int f_ = flow;
                
            if (!flow) {reslen = -1return;}
                re(i, m) 
            if (ed[i].a + n0 == ed[i].b && ed[i].a != s && ed[i].b != t) {
                    deledge(i); deledge(i 
            ^ 1); resu_all();
                    flow 
            = 0while (dfs()) ;
                    
            if (flow < f_) {res[reslen++= ed[i].a + 1; f_--;} else {resuedge(i ^ 1); resuedge(i);}
                }
            }
            void pri()
            {
                
            if (reslen == -1) puts("0"); else if (reslen) {
                    printf(
            "%d\n", reslen);
                    re(i, reslen 
            - 1) printf("%d ", res[i]); printf("%d\n", res[reslen - 1]);
                } 
            else puts("NO ANSWER!");
            }
            int main()
            {
                init();
                solve();
                pri();
                
            return 0;
            }

            posted @ 2011-05-07 14:12 Mato_No1 閱讀(793) | 評(píng)論 (0)編輯 收藏

            【問(wèn)題描述】
            給出一個(gè)圖G和指定的源點(diǎn)s、匯點(diǎn)t,求圖中從點(diǎn)s到點(diǎn)t的第K短路。
            【具體題目】PKU2449(注意:本題有一個(gè)猥瑣之處:不允許空路徑。也就是當(dāng)s等于t時(shí)要將K值加1)
            【算法】
            本題有一種最樸素的辦法:改造Dijkstra,一開始路徑(s, 0)(這里設(shè)路徑(i, l)表示從s到i的一條長(zhǎng)度為l的路徑)入隊(duì)。然后,每次取隊(duì)中長(zhǎng)度最短的路徑,該路徑(i, l)出隊(duì),可以證明,若這是終點(diǎn)為i的路徑第x次出隊(duì),該路徑一定是圖中從s到i的第x短路(若x>K則該路徑已無(wú)用,舍棄)。然后從點(diǎn)i擴(kuò)展,將擴(kuò)展到的路徑全部入隊(duì)。這樣直到終點(diǎn)為t的路徑第K次出隊(duì)即可。
            該算法容易實(shí)現(xiàn)(借助priority_queue),但時(shí)間復(fù)雜度可能達(dá)到O(MK),需要優(yōu)化。
            優(yōu)化:容易發(fā)現(xiàn)該算法其實(shí)有A*的思想,或者說(shuō),該算法其實(shí)是所有結(jié)點(diǎn)的估價(jià)函數(shù)h()值均為0的A*算法。為了優(yōu)化此題,需要將h()值改大。顯然,h(i)值可以設(shè)為從i到t的最短路徑長(zhǎng)度(容易證明它是一致的),然后g(i)=目前結(jié)點(diǎn)代表的路徑長(zhǎng)度,f(i)=g(i)+h(i),然后A*即可。

            注意:更改路徑條數(shù)應(yīng)該在出隊(duì)時(shí)更改,而不能在入隊(duì)時(shí)更改,因?yàn)榭赡茉谠撀窂匠鲫?duì)之前會(huì)有新的比它更短的路徑入隊(duì)。

            代碼(PKU2449):
            #include <iostream>
            #include 
            <stdio.h>
            #include 
            <queue>
            using namespace std;
            #define re(i, n) for (int i=0; i<n; i++)
            const int MAXN = 1500, MAXM = 150000, INF = ~0U >> 2;
            struct edge {
                
            int kk, len, next;
            } ed[MAXM], ed2[MAXM];
            int n, m, s, t, k_, hd[MAXN], tl[MAXN], hd2[MAXN], tl2[MAXN], h[MAXN], q[MAXN + 1], No[MAXN], res = -1;
            bool vst[MAXN];
            struct qnode {
                
            int i, g;
            };
            typedef priority_queue 
            <qnode, vector<qnode> > pq;
            pq z;
            bool operator< (qnode q1, qnode q2)
            {
                
            return q1.g + h[q1.i] > q2.g + h[q2.i];
            }
            void init()
            {
                
            int a0, b0, l0;
                scanf(
            "%d%d"&n, &m);
                re(i, n) hd[i] 
            = tl[i] = hd2[i] = tl2[i] = -1;
                re(i, m) {
                    scanf(
            "%d%d%d"&a0, &b0, &l0); a0--; b0--;
                    ed[i].kk 
            = b0; ed[i].len = l0; ed[i].next = -1;
                    
            if (hd[a0] == -1) hd[a0] = tl[a0] = i; else tl[a0] = ed[tl[a0]].next = i;
                    ed2[i].kk 
            = a0; ed2[i].len = l0; ed2[i].next = -1;
                    
            if (hd2[b0] == -1) hd2[b0] = tl2[b0] = i; else tl2[b0] = ed2[tl2[b0]].next = i;
                }
                scanf(
            "%d%d%d"&s, &t, &k_); --s; --t; k_ += s == t;
            }
            void prepare()
            {
                re(i, n) {h[i] 
            = INF; vst[i] = 0;} h[t] = 0; vst[t] = 1; q[0= t;
                
            int i, h0, j, h1;
                
            for (int front=0, rear=0!(!front && rear == n || front == rear + 1); front == n ? front = 0 : front++) {
                    i 
            = q[front]; h0 = h[i];
                    
            for (int p=hd2[i]; p != -1; p=ed2[p].next) {
                        j 
            = ed2[p].kk; h1 = h0 + ed2[p].len;
                        
            if (h1 < h[j]) {
                            h[j] 
            = h1;
                            
            if (!vst[j]) {vst[j] = 1if (rear == n) q[rear = 0= j; else q[++rear] = j;}
                        }
                    }
                    vst[i] 
            = 0;
                }
            }
            void solve()
            {
                qnode q0; q0.i 
            = s; q0.g = 0; z.push(q0);
                re(i, n) No[i] 
            = 0;
                
            int i, d0, j, d1;
                
            while (!z.empty()) {
                    i 
            = z.top().i; d0 = z.top().g; z.pop();
                    
            if (No[i] >= k_) continue;
                    No[i]
            ++;
                    
            if (i == t && No[i] == k_) {res = d0; break;}
                    
            for (int p=hd[i]; p != -1; p=ed[p].next) {
                        j 
            = ed[p].kk; d1 = d0 + ed[p].len;
                        q0.i 
            = j; q0.g = d1; z.push(q0);
                    }
                }
            }
            void pri()
            {
                printf(
            "%d\n", res);
            }
            int main()
            {
                init();
                prepare();
                solve();
                pri();
                
            return 0;
            }

            posted @ 2011-05-01 15:25 Mato_No1 閱讀(1204) | 評(píng)論 (0)編輯 收藏

                 摘要: 動(dòng)態(tài)區(qū)間最大子序和問(wèn)題【問(wèn)題描述】給出一個(gè)序列A[0..N-1]和M個(gè)操作,每個(gè)操作都是以下三種之一:①:查詢區(qū)間最大子序和操作格式:1 l r表示:查詢A[l..r]內(nèi)的最大子序和(就是A[l..r]內(nèi)的和最大的連續(xù)子序列的和),0<=l<=r<N;②:修改單個(gè)值操作格式:2 i x表示:將A[i]的值改為x,0<=i<N;③:修改整段值操作格式:3 l ...  閱讀全文

            posted @ 2011-04-24 15:50 Mato_No1 閱讀(1667) | 評(píng)論 (2)編輯 收藏

            【問(wèn)題描述】
            給出一個(gè)環(huán)形的字符串S,長(zhǎng)度為N,現(xiàn)在要找到一個(gè)斷開點(diǎn),使得從這里斷開后的字符串字典序最小?;蛘哒f(shuō),對(duì)于長(zhǎng)度為N的字符串S[0..N-1],找到一個(gè)位置i,使得字符串S' = S[i..N-1] + S[0..i-1]的字典序最小。若存在多個(gè)這樣的最優(yōu)斷點(diǎn),則取最左邊(i最小)的那個(gè)。
            【Sample Input】
            amandamanda
            【Sample Output】
            10
            (從第10位斷開后得到的字符串"aamandamand"的字典序是11個(gè)斷開位置中最小的)

            【分析】
            首先將這個(gè)環(huán)形串拆開:只需將S[0..N-1]的后面再接上S[0..N-2]即可(如對(duì)于樣例,可構(gòu)造字符串T = "amandamandaamandamand"),則T的任意一個(gè)長(zhǎng)度為N的子串T[i..i-N+1]就是S從第i位斷開得到的字符串。此時(shí)問(wèn)題就變成了:給出一個(gè)長(zhǎng)度為(2N-1)的字符串,求出其所有長(zhǎng)度為N的子串中字典序最小的

            設(shè)F[x]為T中所有起始位小于N的長(zhǎng)度為x的子串中字典序最小的子串的起始位(若有多個(gè)則取最左邊的),如對(duì)于T="abaabaaababaabaaa",有F[0]=F[1]=0,F(xiàn)[2]=2,F(xiàn)[3]=F[4]=5……本題的目的就是求出F[N]的值。一開始已知的只有F[0]=0(長(zhǎng)度為0的字符串都是空串,字典序都是最小的,取最左邊的第0位)。

            可以發(fā)現(xiàn),F(xiàn)數(shù)組有很多重要的性質(zhì):
            性質(zhì)1 F[0..N]數(shù)組是單調(diào)遞增的。
            證明:用反證法。設(shè)存在一個(gè)值x(0<=x<N)使得F[x]>F[x+1]則根據(jù)定義,有T[F[x+1]..F[x+1]+x]<=T[F[x]..F[x]+x](這里一定不會(huì)越界,即F[x]+x的值一定不大于(2N-1),因?yàn)閤<N,又根據(jù)得F[x]<N,故F[x]+x<2N),這樣,必有T[F[x+1]..F[x+1]+x-1]<=T[F[x]..F[x]+x-1]。然而根據(jù)F[x]的定義又可以得到T[F[x+1]..F[x+1]+x-1]>T[F[x]..F[x]+x-1](否則F[x]的值就應(yīng)該等于F[x+1]的值了),矛盾,故在F[0..N]中不可能存在任何F[x]>F[x+1]的情況,也即F[0..N]數(shù)組是單調(diào)遞增的(以下將F[0..N]數(shù)組簡(jiǎn)稱為F數(shù)組)。
            性質(zhì)2 對(duì)于任意值x(0<=x<N),必然滿足F[x+1]=F[x]或F[x+1]>F[x]+x。
            證明:因?yàn)榍懊嬉呀?jīng)證明了F數(shù)組是單調(diào)遞增的,這里只需證明對(duì)于任意x(0<=x<N),不存F[x]<F[x+1]<=F[x]+x的情況即可。
            這里同樣用反證法。設(shè)存在一個(gè)值x(0<=x<N)使得F[x]<F[x+1]<=F[x]+x。則根據(jù)定義有T[F[x+1]..F[x+1]+x]<T[F[x]..F[x]+x]且T[F[x]..F[x]+x-1]<=T[F[x+1]..F[x+1]+x-1],這樣必有T[F[x]..F[x]+x-1]=T[F[x+1]..F[x+1]+x-1]且T[F[x+1]+x]<T[F[x]+x]。設(shè)D=F[x+1]-F[x],則T[F[x]]=T[F[x]+D],因?yàn)镈<=x,可得T[F[x]+D]=T[F[x]+2D],即T[F[x]]=T[F[x]+2D]。這樣,T[F[x]..F[x]+x-D-1]=T[F[x]+2D..F[x]+x+D-1];又因?yàn)門[F[x]+x-D]=T[F[x]+x],而T[F[x+1]+x](即T[F[x]+x+D]])<T[F[x]+x],這樣,T[F[x]+x+D]<T[F[x]+x-D],也就是,T[F[x]+2D..F[x]+x+D]<T[F[x]..F[x]+x-D]!這樣可以得出,從(F[x]+2D)位開始的任意長(zhǎng)度不小于(x-D)的子串,其字典序都小于從F[x]位開始的同樣長(zhǎng)度的子串,由于F[x]<F[x+1]<=F[x]+x,D=F[x+1]-F[x],所以有1<=D<=x,這樣,F(xiàn)[x]的值就應(yīng)該是(F[x]+2D)了,這顯然不可能。所以,一開始假設(shè)的這種情況是不可能存在的,即對(duì)于任意值x(0<=x<N),必然滿足F[x+1]=F[x]或F[x+1]>F[x]+x。

            根據(jù)F數(shù)組的以上兩個(gè)性質(zhì)可以設(shè)計(jì)出本題的算法:
            設(shè)目前已經(jīng)求出了F[0..x-1]的值,且F[x-1]=i。首先將T[0..i-1]全部刪去(因?yàn)镕數(shù)組是單調(diào)遞增的,F(xiàn)[x]的值一定不小于i),然后對(duì)T自身作擴(kuò)展KMP(就是以T為模板串,T為子串的擴(kuò)展KMP,相當(dāng)于其預(yù)處理部分),一開始先將F[x]置為i,設(shè)第j位的匹配長(zhǎng)度為next[j],若next[j]=x-1且T[j+x-1]<T[i+x-1],則將F[x]的值改為j,這樣掃描一遍,即求出了F[x]的值。若掃描過(guò)程中未出現(xiàn)任何next[j]=x-1,則設(shè)所有next[j]值不小于x的最小next[j]值為y,則可以直接得到F[x..y-1]的值均等于F[x-1]。就這樣直到求出F[N]的值為止。

            時(shí)間復(fù)雜度:O(NÖN),可以根據(jù)性質(zhì)2得到。

            posted @ 2011-04-23 16:09 Mato_No1 閱讀(554) | 評(píng)論 (1)編輯 收藏

            KMP:給出兩個(gè)字符串A(稱為模板串)和B(稱為子串),長(zhǎng)度分別為lenA和lenB,要求在線性時(shí)間內(nèi),對(duì)于每個(gè)A[i](0<=i<lenA),求出A[i]往前和B的前綴匹配的最大匹配長(zhǎng)度,記為ex[i](或者說(shuō),ex[i]為滿足A[i-z+1..i]==B[0..z-1]的最大的z值)。KMP的主要目的是求B是不是A的子串,以及若是,B在A中所有出現(xiàn)的位置(當(dāng)ex[i]=lenB時(shí))。
            【算法】
            設(shè)next[i]為滿足B[i-z+1..i]==B[0..z-1]的最大的z值(也就是B的自身匹配)。設(shè)目前next[0..lenB-1]與ex[0..i-1]均已求出,要用它們來(lái)求ex[i]的值。
            根據(jù)ex的定義,有A[i-1-ex[i-1]+1..i-1]==B[0..ex[i-1]-1],這時(shí),若有A[i]==B[ex[i-1]],則可以直接得到ex[i]=ex[i-1]+1(因?yàn)閕-1-ex[i-1]+1即i-ex[i-1],現(xiàn)在由于A[i]==B[ex[i-1]],可得A[i-ex[i-1]..i]==B[0..ex[i-1]],即A[i-ex[i-1]+1-1..i]==B[0..ex[i-1]+1-1],所以ex[i]=ex[i-1]+1)。若A[i]!=B[ex[i-1]]?
            設(shè)j=next[ex[i-1]-1],則根據(jù)next定義得B[ex[i-1]-j..ex[i-1]-1]==B[0..j-1],又因?yàn)锳[i-ex[i-1]..i-1]==B[0..ex[i-1]-1]得A[i-j..i-1]==B[ex[i-1]-j..ex[i-1]-1],這樣有A[i-j..i-1]==B[0..j-1]!也就是此時(shí)只需再比較A[i]與B[j]的值是否相等即可,若相等,可得ex[i]=j+1,若仍不相等,則更新j為next[j-1],繼續(xù)比較A[i]與B[j]是否相等……直到A[i]與B[j]相等或直到j(luò)==0時(shí),A[i]仍不等于B[j],此時(shí)ex[i]=0。邊界:求ex[0]時(shí),初始j(用來(lái)代替ex[i-1])為0。
            現(xiàn)在還有一個(gè)問(wèn)題,如何求next?顯然next就是以B自身為模板串,B為子串的“自身匹配”,用類似的辦法即可,唯一不同的是next[0]=lenB可以直接得到,求next[1]時(shí),初始j(代替next[i-1])為0。
            【核心代碼】
                lenA = strlen(A); lenB = strlen(B);
                next[
            0= lenB;
                
            int j = 0;
                re2(i, 
            1, lenB) {
                    
            while (j && B[i] != B[j]) j = next[j - 1];
                    
            if (B[i] == B[j]) j++;
                    next[i] 
            = j;
                }
                j 
            = 0;
                re(i, lenA) {
                    
            while (j && A[i] != B[j]) j = next[j - 1];
                    
            if (A[i] == B[j]) j++;
                    ex[i] 
            = j;
                }
            擴(kuò)展KMP:給出模板串A和子串B,長(zhǎng)度分別為lenA和lenB,要求在線性時(shí)間內(nèi),對(duì)于每個(gè)A[i](0<=i<lenA),求出A[i..lenA-1]與B的最長(zhǎng)公共前綴長(zhǎng)度,記為ex[i](或者說(shuō),ex[i]為滿足A[i..i+z-1]==B[0..z-1]的最大的z值)。擴(kuò)展KMP可以用來(lái)解決很多字符串問(wèn)題,如求一個(gè)字符串的最長(zhǎng)回文子串和最長(zhǎng)重復(fù)子串。
            【算法】
            設(shè)next[i]為滿足B[i..i+z-1]==B[0..z-1]的最大的z值(也就是B的自身匹配)。設(shè)目前next[0..lenB-1]與ex[0..i-1]均已求出,要用它們來(lái)求ex[i]的值。
            設(shè)p為目前A串中匹配到的最遠(yuǎn)位置,k為讓其匹配到最遠(yuǎn)位置的值(或者說(shuō),k是在0<=i0<i的所有i0值中,使i0+ex[i0]-1的值最大的一個(gè),p為這個(gè)最大值,即k+ex[k]-1),顯然,p之后的所有位都是未知的,也就是目前還無(wú)法知道A[p+1..lenA-1]中的任何一位和B的任何一位是否相等。
            根據(jù)ex的定義可得,A[k..p]==B[0..p-k],因?yàn)閕>k,所以又有A[i..p]==B[i-k..p-k],設(shè)L=next[i-k],則根據(jù)next的定義有B[0..L-1]==B[i-k..i-k+L-1]??紤]i-k+L-1與p-k的關(guān)系:
            (1)i-k+L-1<p-k,即i+L<=p。這時(shí),由A[i..p]==B[i-k..p-k]可以得到A[i..i+L-1]==B[i-k..i-k+L-1],又因?yàn)锽[0..L-1]==B[i-k..i-k+L-1]所以A[i..i+L-1]==B[0..L-1],這就說(shuō)明ex[i]>=L。又由于next的定義可得,A[i+L]必然不等于B[L](否則A[i..i+L]==B[0..L],因?yàn)閕+L<=p,所以A[i..i+L]==B[i-k..i-k+L],這樣B[0..L]==B[i-k..i-k+L],故next[i-k]的值應(yīng)為L(zhǎng)+1或更大),這樣,可以直接得到ex[i]=L!
            (2)i+k-L+1>=p-k,即i+L>p。這時(shí),首先可以知道A[i..p]和B[0..p-i]是相等的(因?yàn)锳[i..p]==B[i-k..p-k],而i+k-L+1>=p-k,由B[0..L-1]==B[i-k..i-k+L-1]可得B[0..p-i]==B[i-k..p-k],即A[i..p]==B[0..p-i]),然后,對(duì)于A[p+1]和B[p-i+1]是否相等,目前是不知道的(因?yàn)榍懊嬉呀?jīng)說(shuō)過(guò),p是目前A串中匹配到的最遠(yuǎn)位置,在p之后無(wú)法知道任何一位的匹配信息),因此,要從A[p+1]與B[p-i+1]開始往后繼續(xù)匹配(設(shè)j為目前B的匹配位置的下標(biāo),一開始j=p-i+1,每次比較A[i+j]與B[j]是否相等,直到不相等或者越界為止,此時(shí)的j值就是ex[i]的值)。在這種情況下,p的值必然會(huì)得到延伸,因此更新k和p的值。
            邊界:ex[0]的值需要預(yù)先求出,然后將初始的k設(shè)為0,p設(shè)為ex[0]-1。
            對(duì)于求next數(shù)組,也是“自身匹配”,類似KMP的方法處理即可。唯一的不同點(diǎn)也在邊界上:可以直接知道next[0]=lenB,next[1]的值預(yù)先求出,然后初始k=1,p=ex[1]。

            需要嚴(yán)重注意的是,在上述的情況(2)中,本該從A[p+1]與B[p-i+1]開始匹配,但是,若p+1<i,也就是p-i+1<0(這種情況是有可能發(fā)生的,當(dāng)ex[i-1]=0,且前面的ex值都沒有延伸到i及以后的時(shí)候)的話,需要將A、B的下標(biāo)都加1(因?yàn)榇藭r(shí)p必然等于i-2,如果A、B的下標(biāo)用兩個(gè)變量x、y控制的話,x和y都要加1)??!

            【核心代碼】
            lenA = strlen(A); lenB = strlen(B);
                next[
            0= lenB; next[1= lenB - 1;
                re(i, lenB
            -1if (B[i] != B[i + 1]) {next[1= i; break;}
                
            int j, k = 1, p, L;
                re2(i, 
            2, lenB) {
                    p 
            = k + next[k] - 1; L = next[i - k];
                    
            if (i + L <= p) next[i] = L; else {
                        j 
            = p - i + 1;
                        
            if (j < 0) j = 0;
                        
            while (i + j < lenB && B[i + j] == B[j]) j++;
                        next[i] 
            = j; k = i;
                    }
                }
                
            int minlen = lenA <= lenB ? lenA : lenB; ex[0= minlen;
                re(i, minlen) 
            if (A[i] != B[i]) {ex[0= i; break;}
                k 
            = 0;
                re2(i, 
            1, lenA) {
                    p 
            = k + ex[k] - 1; L = next[i - k];
                    
            if (i + L <= p) ex[i] = L; else {
                        j 
            = p - i + 1;
                        
            if (j < 0) j = 0;
                        
            while (i + j < lenA && j < lenB && A[i + j] == B[j]) j++;
                        ex[i] 
            = j; k = i;
                    }
                }
            【時(shí)間復(fù)雜度分析】
            在KMP和擴(kuò)展KMP中,不管是A串還是B串,其匹配位置都是單調(diào)遞增的,故總時(shí)間復(fù)雜度是線性的,都為O(lenA + lenB)(只是擴(kuò)展KMP比KMP的常數(shù)更大一些)。
            【應(yīng)用】
            KMP和擴(kuò)展KMP在解決字符串問(wèn)題中有大用。很多看上去很猥瑣的字符串問(wèn)題,都可以歸結(jié)到這兩種算法之中。另外,這里的“字符串”可以延伸為一切類型的數(shù)組,而不僅僅是字符數(shù)組。

            posted @ 2011-04-17 19:11 Mato_No1 閱讀(5811) | 評(píng)論 (2)編輯 收藏

            放了快2個(gè)月了……(準(zhǔn)確來(lái)說(shuō)放了2年了,本沙茶從2年前就開始捉這道猥瑣題……)
            DLX重復(fù)覆蓋的幾點(diǎn)說(shuō)明:
            (1)必須有啟發(fā)函數(shù)優(yōu)化,否則TLE;
            (2)不需要二分下界,只要將目前f值(f=g+h,即實(shí)際深度加上啟發(fā)函數(shù)值)與目前求得的最優(yōu)解比較即可,f>=最優(yōu)解即剪枝;
            (3)刪一整列(delcol)操作時(shí),可以從任意一個(gè)結(jié)點(diǎn)開始刪(不一定要向精確覆蓋那樣非要從列頭開始),但是,開始的那個(gè)結(jié)點(diǎn)不刪!恢復(fù)(resucol)時(shí)也不恢復(fù)開始的那個(gè)結(jié)點(diǎn)!這是為了在接下來(lái)的橫向遍歷中不受影響。由于不刪開始結(jié)點(diǎn),所以在將最優(yōu)列刪除時(shí),需要循環(huán)一次刪除一次,恢復(fù)一次。

            代碼:(RQNOJ P89)

            #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++)
            #define re3(i, l, r) for (int i=l; i<=r; i++)
            const int MAXN = 61, MAXM = 61, INF = ~0U >> 2;
            struct DLnode {
                
            int r, c, U, D, L, R;
            } d[MAXN 
            * MAXM];
            int n, m, nodes, rowh[MAXN], cols[MAXM], res = INF;
            void init_d()
            {
                re3(i, 
            0, m) {
                    d[i].r 
            = 0; d[i].c = 0; d[i].U = d[i].D = i; d[i].L = i - 1; d[i].R = i + 1;
                }
                d[
            0].L = m; d[m].R = 0;
                memset(rowh, 
            0, n + 1 << 2); memset(cols, 0, m + 1 << 2); nodes = m + 1;
            }
            void add_node(int r, int c)
            {
                d[nodes].r 
            = r; d[nodes].c = c; d[nodes].U = d[c].U; d[nodes].D = c; d[c].U = nodes; d[d[nodes].U].D = nodes;
                
            int rh = rowh[r];
                
            if (rh) {
                    d[nodes].L 
            = d[rh].L; d[nodes].R = rh; d[rh].L = nodes; d[d[nodes].L].R = nodes;
                } 
            else d[nodes].L = d[nodes].R = rowh[r] = nodes;
                cols[c]
            ++; nodes++;
            }
            void init()
            {
                scanf(
            "%d%d"&m, &n);
                init_d();
                
            int k, x;
                re1(i, n) {
                    scanf(
            "%d"&k);
                    re(j, k) {
                        scanf(
            "%d"&x); add_node(i, x);
                    }
                }
            }
            void delUD(int x)
            {
                d[d[x].U].D 
            = d[x].D; d[d[x].D].U = d[x].U;
            }
            void resuUD(int x)
            {
                d[d[x].U].D 
            = d[d[x].D].U = x;
            }
            void delLR(int x)
            {
                d[d[x].L].R 
            = d[x].R; d[d[x].R].L = d[x].L;
            }
            void resuLR(int x)
            {
                d[d[x].L].R 
            = d[d[x].R].L = x;
            }
            void delcol(int c)
            {
                
            for (int i=d[c].D; i != c; i = d[i].D) delLR(i);
            }
            void resucol(int c)
            {
                
            for (int i=d[c].U; i != c; i = d[i].U) resuLR(i);
            }
            int h()
            {
                
            bool vst[MAXM]; memset(vst, 0, m + 1);
                
            int z = 0;
                
            for (int i=d[0].R; i; i = d[i].R) if (!vst[i]) {
                    vst[i] 
            = 1; z++;
                    
            for (int j=d[i].D; j != i; j = d[j].D) for (int k=d[j].R; k != j; k = d[k].R) vst[d[k].c] = 1;
                }
                
            return z;
            }
            void dfs(int v)
            {
                
            if (v + h() >= res) return;
                
            if (!d[0].R) {res = v; return;}
                
            int min = INF, x;
                
            for (int i=d[0].R; i; i = d[i].R) if (cols[i] < min) {min = cols[i]; x = i;}
                
            for (int i=d[x].D; i != x; i = d[i].D) {
                    delcol(i);
                    
            for (int j=d[i].R; j != i; j = d[j].R) delcol(j);
                    dfs(v 
            + 1);
                    
            for (int j=d[i].L; j != i; j = d[j].L) resucol(j);
                    resucol(i);
                }
            }
            void pri()
            {
                printf(
            "%d\n", res);
            }
            int main()
            {
                init();
                dfs(
            0);
                 pri();
                
            return 0;
            }

            總結(jié):DLX精確覆蓋和重復(fù)覆蓋其實(shí)是有大用的,很多搜索問(wèn)題都可以轉(zhuǎn)化為這兩種問(wèn)題,效率奇高無(wú)比,且寫起來(lái)也很容易(只是在建模的時(shí)候可能有點(diǎn)猥瑣,下面的模板,和網(wǎng)絡(luò)流一樣,10min的事),至于NOIP2009引出的數(shù)獨(dú)系列問(wèn)題,精確覆蓋可以直接秒殺。

            posted @ 2011-04-12 22:27 Mato_No1 閱讀(770) | 評(píng)論 (0)編輯 收藏

            圖的連通度問(wèn)題是指:在圖中刪去部分元素(點(diǎn)或邊),使得圖中指定的兩個(gè)點(diǎn)s和t不連通(不存在從s到t的路徑),求至少要?jiǎng)h去幾個(gè)元素。
            圖的連通度分為點(diǎn)連通度和邊連通度:
            (1)點(diǎn)連通度:只許刪點(diǎn),求至少要?jiǎng)h掉幾個(gè)點(diǎn)(當(dāng)然,s和t不能刪去,這里保證原圖中至少有三個(gè)點(diǎn));
            (2)邊連通度:只許刪邊,求至少要?jiǎng)h掉幾條邊。
            并且,有向圖和無(wú)向圖的連通度求法不同,因此還要分開考慮(對(duì)于混合圖,只需將其中所有的無(wú)向邊按照無(wú)向圖的辦法處理、有向邊按照有向圖的辦法處理即可)。

            【1】有向圖的邊連通度:
            這個(gè)其實(shí)就是最小割問(wèn)題。以s為源點(diǎn),t為匯點(diǎn)建立網(wǎng)絡(luò),原圖中的每條邊在網(wǎng)絡(luò)中仍存在,容量為1,求該網(wǎng)絡(luò)的最小割(也就是最大流)的值即為原圖的邊連通度。

            【2】有向圖的點(diǎn)連通度:
            需要拆點(diǎn)。建立一個(gè)網(wǎng)絡(luò),原圖中的每個(gè)點(diǎn)i在網(wǎng)絡(luò)中拆成i'與i'',有一條邊<i', i''>,容量為1(<s', s''>和<t', t''>例外,容量為正無(wú)窮)。原圖中的每條邊<i, j>在網(wǎng)絡(luò)中為邊<i'', j'>,容量為正無(wú)窮。以s'為源點(diǎn)、t''為匯點(diǎn)求最大流,最大流的值即為原圖的點(diǎn)連通度。

            說(shuō)明:最大流對(duì)應(yīng)的是最小割。顯然,容量為正無(wú)窮的邊不可能通過(guò)最小割,也就是原圖中的邊和s、t兩個(gè)點(diǎn)不能刪去;若邊<i, i''>通過(guò)最小割,則表示將原圖中的點(diǎn)i刪去。

            【3】無(wú)向圖的邊連通度:
            將圖中的每條邊(i, j)拆成<i, j>和<j, i>兩條邊,再按照有向圖的辦法(【1】)處理;

            【4】無(wú)向圖的點(diǎn)連通度:
            將圖中的每條邊(i, j)拆成<i, j>和<j, i>兩條邊,再按照有向圖的辦法(【2】)處理。

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

            有向無(wú)環(huán)圖的最小路徑覆蓋問(wèn)題包括兩種(設(shè)G是一個(gè)有向無(wú)環(huán)圖,S是G的一個(gè)路徑集合):
            (1)最小點(diǎn)路徑覆蓋:滿足對(duì)于G中所有的點(diǎn)i,i在S中的一條路徑中出現(xiàn),且只在S中的一條路徑中出現(xiàn),求S的最小容量;
            (2)最小邊路徑覆蓋:滿足對(duì)于G中所有的邊E,E在S中的一條路徑中出現(xiàn),且只在S中的一條路徑中出現(xiàn),求S的最小容量;

            (1)最小點(diǎn)路徑覆蓋:
            建立一個(gè)新圖,將G中的每個(gè)點(diǎn)i在新圖中拆成兩個(gè)點(diǎn)i'、i'',若G中存在邊<i, j>則在新圖中連邊<i', j''>,顯然新圖是一個(gè)二分圖,求其最大匹配,則(N-新圖最大匹配的值)就是最小點(diǎn)路徑覆蓋值。
            時(shí)間復(fù)雜度:O(NM)(Hungary算法)

            (2)最小邊路徑覆蓋:
            對(duì)于圖中的每個(gè)點(diǎn)i,設(shè)D[i]為(i的入度-i的出度)的值,按照D[i]將圖中的點(diǎn)分類:D[i]<0的稱為“入少出多”的點(diǎn),D[i]>0的稱為“出少入多”的點(diǎn),D[i]=0的稱為“入出相等”的點(diǎn)。則有:
            定理 有向無(wú)環(huán)圖中最小邊路徑覆蓋的值等于圖中所有“入少出多”的點(diǎn)的D值之和。
            證明:
            其實(shí)只需證明:對(duì)于一個(gè)至少有一條邊的有向無(wú)環(huán)圖,必然存在一條路徑,其起點(diǎn)是“入少出多”的點(diǎn),終點(diǎn)是“出少入多”的點(diǎn),所有中間點(diǎn)都是“入出相等”的點(diǎn)(只要不斷的在圖中找到并刪去這條路徑,直到圖中無(wú)邊為止)。
            首先,由于圖中無(wú)環(huán),一定存在“入少出多”的點(diǎn)和“出少入多”的點(diǎn)。
            然后,假設(shè)圖中所有“入少出多”的點(diǎn)的后繼(注意:后繼和后代是不同的,一個(gè)點(diǎn)的后代包括這個(gè)點(diǎn)的所有后繼、所有后繼的后繼、所有后繼的后繼的后繼……)也都是“入少出多”的點(diǎn),則圖中所有“入少出多”的點(diǎn)構(gòu)成了一個(gè)閉合子圖,在這個(gè)閉合子圖中,由于所有的點(diǎn)都是“入少出多”的,整個(gè)子圖的入度之和必然大于出度之和,這是不可能的(有向圖中的所有點(diǎn)入度之和必然等于所有點(diǎn)出度之和),故可得:圖中必然存在至少一個(gè)“入少出多”的點(diǎn),它有不是“入少出多”的后繼。
            這樣,在這些擁有不是“入少出多”的后繼的點(diǎn)中選擇一個(gè)點(diǎn)i,將其作為路徑的起點(diǎn),在它的不是“入少出多”的后繼中選出一個(gè)點(diǎn)j,若j是“出少入多”的點(diǎn),則邊<i, j>就是符合條件的一條路徑,結(jié)束;若這樣的j都是“入出相等”的點(diǎn),則考慮j的后代:j的后繼可能都是“入少出多”的,但j的后代中必然存在至少一個(gè)點(diǎn)j'不是“入少出多”的(否則j的所有后代也將構(gòu)成全都是“入少出多”的閉合子圖),這些j'中必然存在一個(gè)點(diǎn)的前趨i'是“入少出多”的,這是,需要舍棄原來(lái)的路徑,將i'作為新路徑的起點(diǎn),j'作為新路徑的第一個(gè)中間點(diǎn),繼續(xù)找;若j的后繼不全是“入少出多”的,則若其中有“出少入多”的則路徑已找到,若都是“入出相等”的,由于圖中無(wú)環(huán),將路徑不斷延伸,最終一定會(huì)找到合法路徑。

            由此可得,對(duì)于任何有向無(wú)環(huán)圖,這樣的路徑都是存在的,也就證明了一開始的求最小邊路徑覆蓋值的定理。
            求有向無(wú)環(huán)圖最小邊路徑覆蓋值的時(shí)間復(fù)雜度:O(M+N)。

            posted @ 2011-04-05 16:04 Mato_No1 閱讀(2078) | 評(píng)論 (0)編輯 收藏

            僅列出標(biāo)題
            共12頁(yè): First 4 5 6 7 8 9 10 11 12 
            青青久久精品国产免费看| 国产亚洲精品久久久久秋霞| 久久精品综合一区二区三区| 欧美精品丝袜久久久中文字幕 | 亚洲欧洲久久久精品| 伊人久久大香线焦AV综合影院 | 久久无码人妻精品一区二区三区| 奇米影视7777久久精品人人爽| 久久成人国产精品| 久久青青草原精品国产软件| 亚洲午夜久久久久妓女影院| 久久精品国产免费一区| 97精品伊人久久大香线蕉| 国产精品久久久久久福利漫画| 综合久久精品色| 91精品久久久久久无码| 国产亚洲精久久久久久无码77777| 99久久国产主播综合精品| 亚洲国产精品久久电影欧美| 久久人人超碰精品CAOPOREN| 99久久久国产精品免费无卡顿| 亚洲欧美国产精品专区久久 | 国产香蕉97碰碰久久人人| 午夜精品久久久久久中宇| 久久婷婷五月综合色99啪ak| 青青草国产成人久久91网| 久久精品国产男包| 久久青青国产| 狠狠精品干练久久久无码中文字幕| 久久精品国产亚洲AV嫖农村妇女| 亚洲精品tv久久久久| 精品久久久久久无码国产| www.久久99| 久久Av无码精品人妻系列| 伊人久久成人成综合网222| 中文字幕久久欲求不满| 1000部精品久久久久久久久| 浪潮AV色综合久久天堂| 久久久久波多野结衣高潮| 亚洲精品成人久久久| 久久露脸国产精品|