標籤:blog 演算法 for 代碼 io 時間
在這裡我只放我的模板和一些我個人的“理解”。。
最大流測試題:usaco草地排水
EK:
時間複雜度:O(VE^2)
代碼複雜度:最易
代碼:
#include <cstdio>#include <cstring>#include <queue>using namespace std;#define CLR(a) memset(a, 0, sizeof(a))#define FOR(i,a,n) for(i=a;i<=n;++i)const int maxn = 205, oo = ~0u >> 1;int cap[maxn][maxn], ihead[maxn], inext[maxn<<2], iv[maxn<<2], cnt;int p[maxn];queue<int> q;int n, m;int min(const int& a, const int& b) { return a < b ? a : b; }int EK(int s, int t) {int u, v, i, flow = 0, aug;while(1) {CLR(p);q.push(s);while(!q.empty()) {u = q.front(); q.pop();for(i = ihead[u]; i ; i = inext[i]) if(cap[u][iv[i]] > 0 && !p[iv[i]]) {p[iv[i]] = u; q.push(iv[i]);}}if(!p[t]) break;aug = oo;for(v = t; v != s; v = p[v]) aug = min(aug, cap[p[v]][v]);for(v = t; v != s; v = p[v]) cap[p[v]][v] -= aug, cap[v][p[v]] += aug;flow += aug;}return flow;}void add(int u, int v, int c) {inext[++cnt] = ihead[u]; ihead[u] = cnt; iv[cnt] = v;cap[u][v] += c;inext[++cnt] = ihead[v]; ihead[v] = cnt; iv[cnt] = u;}int main() {scanf("%d%d", &m, &n);int u, v, c; for(int i = 1; i <= m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); }printf("%d", EK(1, n));return 0;}
Dinic:
時間複雜度:O(V^2E)
代碼複雜度:較長但不易出錯
PS:這是加了當前弧最佳化的
代碼2(這份代碼的cap是二維的):
#include <cstdio>#include <cstring>#include <queue>using namespace std;#define CLR(a) memset(a, 0, sizeof(a))#define FOR(i,a,n) for(i=a;i<=n;++i)const int maxn=205, oo=~0u>>1, maxm=maxn<<2;int cap[maxn][maxn], ihead[maxn], inext[maxm], iv[maxm], cnt;int vis[maxn], d[maxn], cur[maxn];queue<int> q;int n, m, s, t;int min(const int& a, const int& b) { return a < b ? a : b; }bool BFS() { //建立層次圖CLR(vis);q.push(s); d[s]=0; vis[s]=1; //這裡記住要加上vis[s]=1;int u, i;while(!q.empty()) {u=q.front(); q.pop();for(i=ihead[u]; i; i=inext[i]) if(!vis[iv[i]] && cap[u][iv[i]]) { d[iv[i]]=d[u]+1; vis[iv[i]]=1; q.push(iv[i]); }}return vis[t];}int DFS(int u, int a) {if(u==t || !a) return a;int f, flow=0;for(int& i=cur[u]; i; i=inext[i]) if(d[u]+1==d[iv[i]] && (f=DFS(iv[i], min(a, cap[u][iv[i]])))>0 ) { //沿著層次圖走,並且這是條增廣路flow+=f, a-=f, cap[u][iv[i]]-=f, cap[iv[i]][u]+=f;//a儲存的是之前路徑的最小,所以不一定是等於f,所以要減掉fif(!a) break; //說明不可能再有增廣路,也就是說,前面斷了。}return flow;}int dinic() {int flow=0, i;while(BFS()) {FOR(i, 1, n) cur[i]=ihead[i];flow+=DFS(s, oo);}return flow;}void add(int u, int v, int c) {inext[++cnt]=ihead[u]; ihead[u]=cnt; iv[cnt]=v;cap[u][v]+=c;inext[++cnt]=ihead[v]; ihead[v]=cnt; iv[cnt]=u;}int main() {scanf("%d%d", &m, &n);int u, v, c; for(int i=1; i<=m; ++i) { scanf("%d%d%d", &u, &v, &c); add(u, v, c); } s=1; t=n;printf("%d", dinic());return 0;}
代碼2(cap已改為1維,並做了相應更改):
#include <cstdio>#include <cstring>#include <queue>using namespace std;#define CLR(a) memset(a, 0, sizeof(a))#define FOR(i,a,n) for(i=a;i<=n;++i)const int maxn=205, oo=~0u>>1, maxm=maxn<<2;int cap[maxm], ihead[maxn], inext[maxm], to[maxm], cnt=1; //聽取wyl意見,從2開始編號int vis[maxn], d[maxn], cur[maxn];queue<int> q;int n, m, s, t;int min(const int& a, const int& b) { return a < b ? a : b; }bool BFS() { //建立層次圖CLR(vis);q.push(s); d[s]=0; vis[s]=1; //這裡記住要加上vis[s]=1;int u, i;while(!q.empty()) {u=q.front(); q.pop();for(i=ihead[u]; i; i=inext[i]) if(!vis[to[i]] && cap[i]) { d[to[i]]=d[u]+1; vis[to[i]]=1; q.push(to[i]); }}return vis[t];}int DFS(int u, int a) {if(u==t || !a) return a;int f, flow=0;for(int& i=cur[u]; i; i=inext[i]) if(d[u]+1==d[to[i]] && (f=DFS(to[i], min(a, cap[i])))>0 ) { //沿著層次圖走,並且這是條增廣路flow+=f, a-=f, cap[i]-=f, cap[i^1]+=f;//a儲存的是之前路徑的最小,所以不一定是等於f,所以要減掉fif(!a) break; //說明不可能再有增廣路,也就是說,前面斷了。}return flow;}int dinic() {int flow=0, i;while(BFS()) {FOR(i, 1, n) cur[i]=ihead[i];flow+=DFS(s, oo);}return flow;}void add(int u, int v, int c) {inext[++cnt]=ihead[u]; ihead[u]=cnt; to[cnt]=v; cap[cnt]=c;inext[++cnt]=ihead[v]; ihead[v]=cnt; to[cnt]=u;}int main() {scanf("%d%d", &m, &n);int u, v, c, i;for(i=1; i<=m; ++i) {scanf("%d%d%d", &u, &v, &c);add(u, v, c);}s=1; t=n;printf("%d", dinic());return 0;}
isap(國人都叫sap,其實百度一下有解釋的):
時間複雜度:O(V^2E)實際快多了,這是我將來用的演算法
代碼複雜度:短但細節太多,易錯
PS:這是加了當前弧最佳化、gap最佳化的、暫無瓶頸最佳化 | 話說,調sap我都調怕了,有點恐懼症了。。
代碼1(容量我還是用二維的存):
#include <cstdio>#include <cstring>#include <queue>using namespace std;#define CC(a, c) memset(a, c, sizeof(a))#define FOR(i,a,n) for(i=a;i<=n;++i)const int maxn=205, oo=~0u>>1, maxm=maxn<<2;int cap[maxn][maxn], ihead[maxn], inext[maxm], iv[maxm], cnt;int p[maxn], d[maxn], cur[maxn], gap[maxn];int n, m, s, t;int min(const int& a, const int& b) { return a < b ? a : b; }int isap() {int flow=0, u, v, i, f=oo;CC(d, 0); CC(gap, 0);for(i=1; i<=n; ++i) cur[i]=ihead[i];u=p[s]=s; gap[0]=n; //這裡預先處理很重要,第一個賦值給p[s]是因為後面會調用,第二個是因為沒有單獨的bfs來處理d數組才要這樣設定,否則一兩次迴圈就要跳出了while(d[s]<n) {for(i=cur[u]; i; i=inext[i]) if(cap[u][iv[i]] && d[u]==d[iv[i]]+1) break;if(i) {cur[u]=i; v=iv[i]; f=min(f, cap[u][v]); p[v]=u; u=v; //這裡記得將u重新賦值過,cur當前弧最佳化if(v==t) {for(; v!=s; v=p[v]) cap[p[v]][v]-=f, cap[v][p[v]]+=f;u=s; flow+=f; f=oo; //將u退回遠點,似乎有叫瓶頸最佳化的東西,到時候再說吧。記得將最小流f重設}}else {if( !(--gap[d[u]]) ) break; //gap最佳化,即發現斷層立即終止d[u]=n;//當沒有發現允許弧時,要重新設定cur[u]=ihead[u];//這裡最坑,,,還是請大神幫忙調試出來的0 0,應該這裡是瓶頸最佳化的地方for(i=cur[u]; i; i=inext[i]) if(cap[u][iv[i]] && d[u]>d[iv[i]]+1) d[u]=d[iv[i]]+1;gap[d[u]]++; u=p[u];}}return flow;}void add(int u, int v, int c) {inext[++cnt]=ihead[u]; ihead[u]=cnt; iv[cnt]=v; cap[u][v]+=c;inext[++cnt]=ihead[v]; ihead[v]=cnt; iv[cnt]=u;}int main() {scanf("%d%d", &m, &n);int u, v, c, i;for(i=1; i<=m; ++i) {scanf("%d%d%d", &u, &v, &c);add(u, v, c);}s=1; t=n;printf("%d", isap());return 0;}
代碼2(已改為1維並相應修改):
#include <cstdio>#include <cstring>#include <queue>using namespace std;#define CC(a, c) memset(a, c, sizeof(a))#define FOR(i,a,n) for(i=a;i<=n;++i)const int maxn=205, oo=~0u>>1, maxm=maxn<<2;int cap[maxm], ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt=1; //聽取wyl意見,從2開始編號int p[maxn], d[maxn], cur[maxn], gap[maxn];int n, m, s, t;int min(const int& a, const int& b) { return a < b ? a : b; }int isap() {int flow=0, u, v, i, f=oo;CC(d, 0); CC(gap, 0);for(i=1; i<=n; ++i) cur[i]=ihead[i];u=s; gap[0]=n; //這裡預先處理很重要,第一個賦值給p[s]是因為後面會調用,第二個是因為沒有單獨的bfs來處理d數組才要這樣設定,否則一兩次迴圈就要跳出了while(d[s]<n) {for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;if(i) {cur[u]=i; v=to[i]; f=min(f, cap[i]); p[v]=i; u=v; //這裡記得將u重新賦值過,cur當前弧最佳化 //這裡注意,p要賦值成iif(v==t) {for(; v!=s; v=from[p[v]]) cap[p[v]]-=f, cap[p[v]^1]+=f;u=s; flow+=f; f=oo; //將u退回遠點,似乎有叫瓶頸最佳化的東西,到時候再說吧。記得將最小流f重設}}else {if( !(--gap[d[u]]) ) break; //gap最佳化,即發現斷層立即終止d[u]=n;//當沒有發現允許弧時,要重新設定cur[u]=ihead[u];//這裡最坑,,,還是請大神幫忙調試出來的0 0,應該這裡是瓶頸最佳化的地方for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1) d[u]=d[to[i]]+1;gap[d[u]]++; if(u!=s) u=from[p[u]]; //這裡要注意}}return flow;}void add(int u, int v, int c) {inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c;inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0;}int main() {scanf("%d%d", &m, &n);int u, v, c, i;for(i=1; i<=m; ++i) {scanf("%d%d%d", &u, &v, &c);add(u, v, c);}s=1; t=n;printf("%d", isap());return 0;}
我自己的瞎扯:
- 網路流為什麼要有反向弧:最直觀的是= =,如果自己原來的弧的點被其它的s-t佔掉了,還可以通過反向弧從佔掉那個點的弧(u,v)中的u流回去,去占它的點,從而構成一條新的s-t
- isap的Gap最佳化的解釋:
斷層這個概念,可以這樣想:
b
/
a -- c
\
d
這裡我們假設d(a)=5
此時假設b後的增廣路已經完了,那麼d(b) = n,但是我們發現,還有d(c)=4,即沒有斷層。
一直下去,直到d(c)和d(d)都為n了,此時沒有一個d(i)=4,所以,發生斷層,此時d(i)=4的數目為0,所以就可以結束程式了。
這就是所謂的Gap最佳化~
- 網路流這一類題的初始化:
首先就要將所有點和該連的弧給連了,否則容易出錯,什麼拆點啊這些操作首先搞掉。
- 網路流上下界的胡扯解釋:
為什麼一條邊(u,v)的的下界是從附加源連到v呢?
我是這樣認為的: 從源開始流,又流回了u,再流回匯,如果滿了,就能判斷了0.0
同理,將u連到了匯上。。而匯到源連一條無限容量的弧是為了讓那個下界流從這裡流回去。。