最近一直想搞定這道題目。看了discuss才知道是網路流的題目,好多人說可以用二分圖匹配做,這個還沒想明白,還是看看經典的網路流吧。以前學習過相關的知識,但是由於學習的時間比較久,加上當時也是學的馬馬虎虎,所以導致現在對網路流完全不理解。這段時間,抽時間看了一些網上的資料,加上演算法導論上的一些解釋,也算整明白了。不敢獨享,在這裡晒晒自己學到的東西,分享。。。
網路流基礎知識:
本程式用的就是Dinic演算法,這裡介紹一下Dinic演算法
虛擬碼表示:
下面是虛擬碼的具體代碼:
// max net stream#include <cstdio>#include <cstring>#include <map>#include <string>#include <vector>#include <iostream>using namespace std;map<string, int>mp;vector<int>G[800];//無向圖int n, m, k;int que[800];int d[800];int stack[800];int g[800][800];//有向流網路bool v[800];//尋找增廣層次路徑bool Bfs(int s, int t){int l = 0;int r = 0;int i, p;memset(d, -1, sizeof(d));d[s] = 0;que[r ++] = s;while (l < r){p = que[l ++];for (i = 0; i < G[p].size(); ++ i){int u = G[p][i];if (g[p][u] > 0 && d[u] == -1){d[u] = d[p] + 1;//增廣最短路if (u == t){return true;}que[r ++ ] = u;}}}return false;}int Dinic_Maxflow(int s, int t){int i , p;int nMinc;int minn;int ans = 0;// 找增廣路徑,直到找不到為止while (Bfs(s, t)){int top = 0, base = 0;memset(v, false, sizeof(v));stack[top ++] =s;v[s] = true;while (top > base){p = stack[top - 1];if (p == t)//找到一條{minn = 0xfffffff;for(i = 1; i < top; ++ i)//求出增廣路上的最小值{int u = stack[i - 1];int v = stack[i];if (g[u][v] < minn){minn = g[u][v];nMinc = u;}}ans += minn;//增加流量for (i = 1; i < top; ++ i)//更新增廣路{int u = stack[i - 1];int v = stack[i];g[u][v] -= minn;g[v][u] += minn;}while (top > base && stack[top - 1] != nMinc)//重新回到標號點,一次標號,多次增廣{top --;}}else//找到增廣路{for (i = 0; i < G[p].size(); ++ i){int u = G[p][i];if (! v[u] && g[p][u] > 0 && d[u] == d[p] + 1){v[u] = true;stack[top ++] = u;break;}}if (i == G[p].size()){top --;}}}}return ans;}int main(){int index;int tot;char str[30],ss[30];while (scanf("%d", &n) != EOF){index = 1;mp.clear();memset(g, 0 ,sizeof(g));memset(G, 0, sizeof(G));//插座和匯點串連while (n --){scanf("%s", str);mp[str] = index ++;G[mp[str]].push_back(799);g[mp[str]][799] = 1;}scanf("%d", &m);tot = m;//裝置和源點串連while (m --){scanf("%s%s", ss, str);mp[ss] = index ++;g[0][mp[ss]] = 1;if (! mp[str]){mp[str] = index ++;}G[0].push_back(mp[ss]);G[mp[ss]].push_back(mp[str]);G[mp[str]].push_back(mp[ss]);g[mp[ss]][mp[str]] = 1;}scanf("%d", &k);//轉接點while (k --){scanf("%s%s", ss, str);if (! mp[str]){mp[str] = index ++;}G[mp[ss]].push_back(mp[str]);G[mp[str]].push_back(mp[ss]);g[mp[ss]][mp[str]] = 0xfffffff;}printf("%d\n", tot - Dinic_Maxflow(0, 799));}return 0;}這裡主要做兩點解釋吧,估計也是很多人不明白的地方1、為何找到一條增廣路徑中的最小值時,正向減去這個值,反向加上這個值。在這幅圖中我們首先要增廣1->2->4->6,這時可以獲得一個容量為2的流,但是如果不建立4->2反向弧的話,則無法進一步增廣,最終答案為2,顯然是不對的,然而如果建立了反向弧4->2,則第二次能進行1->3->4->2->5->6的增廣,最大流為3.Comzyh對反向弧的理解可以說是"
偷梁換柱",請仔細閱讀:在上面的例子中,我們可以看出,最終結果是1->2->5->6和1->2->4->6和1->3->4->6.當增廣完1->2->4->5(代號A)後,在增廣1->3->4->2->5->6(代號B),相當於將經過節點2的A流從中截流1(總共是2)走2->5>6,而不走2->4>6了,同時B流也從節點4截流出1(總共是1)走4->6而不是4->2->5->6,相當於AB流做加法.簡單的說反向弧為今後提供反悔的機會,讓前面不走這條路而走別的路.2、為何找到一條以後,退到最小值那個點這就是為了提高效率,減少不必要的搜尋。也就是當我們找到一條最短的路徑的時候,可以一次搜尋,多次增廣,一直到這條不能增廣為止。然後再去尋找下一條。
比如有1---8個點構成的圖。 1-4-5-6-7-8是最短的,而5-6是最短的。下一個是6-7,如果繼續搜尋,那就是搜完第一條,直接回搜到6-7了。否則我們可能在下一次,1-2-3-4-6-7-8這樣就多了1 -2-3-4這些搜尋。總之就是盡量將一條最短路上的增廣找完。自己畫畫吧。。。。
不早了, gg, 睡覺。。。。。下次再細細學習。