【不定期更新篇(未成形篇)】
一直在搞,就像吃很惡習的東西,好想吐;
但還是可以理解成良藥苦口啊~;
基礎知識定義:
網絡是一種特殊的有向圖。
有向加權圖G,指定兩個定點S和T,分別稱爲源和匯。邊上的權值稱爲容量;
網絡中的一個可行流並不是改路線中所有邊的容量的簡單相加,這段路線的總承重量受路線中最小容量邊的制約。
尋找方案使得總運輸量最大,這就是網絡流算法需要解決的問題;
通俗的講:(我將說路就是管道,路的權值就是管道的容量)
在一個網絡中,往源點灌水,然後從源點水開始流向匯點,
【只考慮在單位時間下】
1.如果從大的流分流,分開的流的總和是小於這個流的,那麼支流的流量就是本身的容量,見下圖(1);
2.如果從小的流往大的容量的管道,那麼對於大的管道來說,水流過來才這麼點,所以大的管道也才只有之前的那麼多水而已啊,見圖(2);
一個網絡中的最大流就是指該網絡中流值最大的流,只是指了某條特定的流,而我們是要通過方法來計算這個最大流的流值是多大。
感覺先搞一下網絡流算法EK算法的流程好像還是不懂,真的好無力啊。
可以先打一遍!!!(推薦);
大致流程就是BFS一下,找到一條增廣路,搞出一個最小的流值,從源到匯上的路的權值減去這個流值,並且在匯到源上權值加上這個流值;
FF方法的具體步驟(摘自圖論書):
(1):初始化網絡中所有邊的容量,c<u,v>繼承該邊的容量,c<v,u>初始化爲0,其中邊<v,u>即爲回退邊。初始化最大流爲0;
(2):在殘留網絡中找出一條從源S到匯T的增廣路p。如果能找到,則轉步驟(3);
如不能找到,則轉步驟(5).
(3):在增廣路p中找出所謂的“瓶頸”邊,即路徑中容量最小的邊,記錄下這個值X,並且累加到最大流中,轉步驟(4)。
(4):將增廣路中所有c< u , v >減去X,所有c<v,u>加上X,構成新的殘留網絡。轉步驟(2).
(5):得到網絡的最大流,退出。
const intN=110; //最大點個數
intma[N][N],n,p[N]; //ma:鄰接數組;n:點數;p:前驅數組
bool EK_bfs()
{
queue<int>q;//隊列
bool flag[N];//標記數組
//初始化
memset(flag,false,sizeof(flag));
memset(p,-1,sizeof(p));
while(!q.empty())
q.pop();
flag[1]=true;
q.push(1);
while(!q.empty())
{
int u=q.front();
q.pop();
if(u==m) //當隊列彈出的點爲終點時即可判增廣路已經找到;
return true;
for(int i=1;i<=m;i++)
{
if(ma[u][i]&&!flag[i])//當邊容量非0,且增廣點未標記
{
flag[i]=true;
p[i]=u; //記錄前驅
q.push(i);
}
}
}
return false;
}
int EK()
{
int u,ans=0,mm;
while(EK_bfs())//當增廣成功的時候
{
mm=INF;
u=m;
while(p[u]!=-1)
{
mm=min(mm,ma[p[u]][u]);
u=p[u]; //尋找到“瓶頸”邊,並且記錄容量(也可以在BFS過程中求出)
}
ans+=mm; //累加進最大流
u=m;
while(p[u]!=-1) //修改路徑上邊的容量;
{
ma[p[u]][u]-=mm;
ma[u][p[u]]+=mm;
u=p[u];
}
}
return ans;
}
感覺打完了一發。。。還是好難理解是不是,模擬一發吧。。。
原題鏈接(http://poj.org/problem?id=1273)
給你4個點,5條邊;
5 4
1 2 40//代表1到2有40的容量
1 4 20
2 4 20
2 3 30
3 4 10
然後模擬(我就記錄個flow的結果,細節上的自行模擬了…)
一開始進去:
初始化,
從源 1 開始,
1->2,flow[2]=40; 2入隊;
1->4,flow[4]=20; 4入隊;
2->3,flow[3]=min(flow[2],ma[2][3])=20;
2->4,不需要,4已經處理過了
4出隊=匯點,返回;
然後回溯一下:找到一個最小的流,然後更新,將找到的增廣路把這個殘留網絡額外的流減去,反向邊加上這個值
……
一直循環到找不到增廣路爲止;
可惜。。。真心好難理解啊。。打完還是似懂非懂,其實最重要的兩步就是BFS找增廣路,然後構造一下殘留網路,一直循環,然後直到找不到增廣路,當前的流值,就是最大流;
然後理解Dinic算法,推薦:Comzyh的博客
基本流程:
1.根據殘留網絡計算層次圖;
2.在層次圖中使用DFS進行增廣直到不存在增廣路
3.重複以上步驟直到無法增廣
--------補充說明一個找增廣的問題;
給出這樣一副圖:
先找到的增廣有:
路 流量 轉化成 路 流量 路 流量
1->2 2 1<-2 2 1->2 0
2->4 2 4<-2 2 2->4 0
4->6 2 6<-4 2 4->6 0
ok,這樣就是找到一個完全的增廣;ans+=2;
繼續;
1->3 1 3<-1 1 1->3 0
3->4 1 4<-3 1 3->4 0
4->2 2 2->4 1 4->2 1
2->5 1 5->2 1 2->5 0
5->6 1 6->5 1 5->6 0
ok,又是一個增廣 ;ans+=1;
終點闡述反向弧的作用:
反向弧可以理解我給這條路分配了x的流量,我建立一個反向弧,等下給自己一個後悔的機會。
比如上面那個第一次找增廣過程中4->2建立反向弧,在第二次找增廣的時候利用了這個反向弧;
我們可以理解之前我先安排2單位水通過這條路,不要理解成是這條路給的流量,流量都是從源點出發的,很自然這條路上的流量是上面那條路的流量流過來。
對於第一次,留多少呢,我先流個2吧,然後給自己一個機會(建立一個反向弧),等會可能我往這個方向不流2,比如第二次找到了,我可以去別的方向流1,那多個機會流還不好啊,形象上的理解就是上面那個點出來的分流了;
那麼還有一個問題,怎麼理解反向弧前面的那些路呢?
是不是可以理解這條路(3->4)的流能到達路(2->4)到達4這個點,然而4以後已經阻斷了,或者說已經塞了一部分流量了,
然後呢可以利用路(2->4)的反向弧(4->2),索取一部分他的,後悔以後就是說前面那個點(2)在這裏分流了,注意每次找的增廣都是一個能實現的圖上的一個"瓶頸",
然後點(2)分流,實際上是給路(2->5)1單位,給路(2->4)1單位,然後路(3->4)往點4運了1單位,4->6流向6就是2單位;
貼一發DINIC的模板;
鄰接矩陣版本:
const int INF=0x3f3f3f3f;
const int N=1e3+10;
int ma[N][N];
int dis[N];
int q[N*100],h,r;
int n,m,ans;
bool BFS()
{
int i,j;
memset(dis,-1,sizeof(dis));
dis[1]=0;
h=0,r=1;
q[1]=1;
while(h<r)
{
j=q[++h];
for(i=1;i<=n;i++)
{
if(dis[i]<0&&ma[j][i]>0)
{
dis[i]=dis[j]+1;
q[++r]=i;
}
}
}
if(dis[n]>0)
return true;
return false;
}
int DFS(int x,int low)
{
int i,a=0;
if(x==n)
return low;
for(int i=1;i<=n;i++)
{
if(ma[x][i]>0&&dis[i]==dis[x]+1&&(a=DFS(i,min(low,ma[x][i]))))
{
ma[x][i]-=a;
ma[i][x]+=a;
return a;
}
}
return 0;
}
void DINIC()
{
int tans;
ans=0;
while(BFS())
{
while(tans=DFS(1,INF))
ans+=tans;
}
printf("%d\n",ans);
}
前向星版本:HDU(3549)
#include<bits/stdc++.h>
using namespace std;
typedef __int64 LL;
const int INF=0x3f3f3f3f;
const int N=2050;
int level[N];
int q[N],h;
int n,m,ans;
struct asd{
int to;
int w;
int next;
};
asd edge[2500000];
int head[2500000],tol;
bool BFS(int s,int t)
{
int i,k;
int top;
memset(level,0,sizeof(level));
level[s]=1;
h=0;
q[h++]=s;
for(int i=0;i<h;i++)
{
top=q[i];
if(top==t)
return true;
for(k=head[top];k!=-1;k=edge[k].next)
{
if(!level[edge[k].to]&&edge[k].w>0)
{
q[h++]=edge[k].to;
level[edge[k].to]=level[top]+1;
}
}
}
return false;
}
int DFS(int now,int maxw,int t)
{
int w,k,ret=0;
if(now==t)
return maxw;
for(k=head[now];k!=-1;k=edge[k].next)
{
if(edge[k].w>0&&level[edge[k].to]==level[now]+1)
{
w=DFS(edge[k].to,min(maxw-ret,edge[k].w),t);
edge[k].w-=w;
edge[k^1].w+=w;
ret+=w;
if(ret==maxw)
return ret;
}
}
return ret;
}
void DINIC()
{
ans=0;
while(BFS(1,n))
ans+=DFS(1,INF,n);
printf("%d\n",ans);
}
void add(int a,int b,int c)
{
edge[tol].w=c;
edge[tol].to=b;
edge[tol].next=head[a];
head[a]=tol++;
}
int main()
{
int T,cas=1;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
int u,v,x;
tol=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&x);
add(u,v,x);
add(v,u,0);
}
printf("Case %d: ",cas++);
DINIC();
}
return 0;
}
SAP 最大流 雙向圖模板
//#include <bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<queue>
#include<cmath>
#include<algorithm>
typedef long long LL;
using namespace std;
//#pragma comment(linker, "/STACK:102400000,102400000")
/*
給出起點,終點,邊數。
然後雙向邊(u,v,w)
求最大流;
*/ const int INF = 0x3f3f3f3f; const int MAXN = 110;//點數的最大值 const int MAXM = 100010;//邊數的最大值 struct Edge { int to, next, cap, flow; }edge[MAXM];//注意是MAXM int tol; int head[MAXN]; int gap[MAXN], dep[MAXN], pre[MAXN], cur[MAXN]; void init() { tol = 0; memset(head, -1, sizeof(head)); } //加邊,單向圖三個參數,雙向圖四個參數 void addedge(int u, int v, int w, int rw = 0) { edge[tol].to = v; edge[tol].cap = w; edge[tol].next = head[u]; edge[tol].flow = 0; head[u] = tol++; edge[tol].to = u; edge[tol].cap = rw; edge[tol].next = head[v]; edge[tol].flow = 0; head[v] = tol++; } //輸入參數:起點、終點、點的總數 //點的編號沒有影響,只要輸入點的總數 int sap(int start, int end, int N) { memset(gap, 0, sizeof(gap)); memset(dep, 0, sizeof(dep)); memcpy(cur, head, sizeof(head)); int u = start; pre[u] = -1; gap[0] = N; int ans = 0; while (dep[start] < N) { if (u == end) { int Min = INF; for (int i = pre[u]; i != -1; i = pre[edge[i ^ 1].to]) if (Min > edge[i].cap - edge[i].flow) Min = edge[i].cap - edge[i].flow; for (int i = pre[u]; i != -1; i = pre[edge[i ^ 1].to]) { edge[i].flow += Min; edge[i ^ 1].flow -= Min; } u = start; ans += Min; continue; } bool flag = false; int v; for (int i = cur[u]; i != -1; i = edge[i].next) { v = edge[i].to; if (edge[i].cap - edge[i].flow && dep[v] + 1 == dep[u]) { flag = true; cur[u] = pre[v] = i; break; } } if (flag) { u = v; continue; } int Min = N; for (int i = head[u]; i != -1; i = edge[i].next) if (edge[i].cap - edge[i].flow && dep[edge[i].to] < Min) { Min = dep[edge[i].to]; cur[u] = i; } gap[dep[u]]--; if (!gap[dep[u]])return ans; dep[u] = Min + 1; gap[dep[u]]++; if (u != start) u = edge[pre[u] ^ 1].to; } return ans; } int m, n, s, t; int a, b, c; int main() { int T, cases = 1; scanf("%d", &T); while (T--) { init(); scanf("%d", &n); scanf("%d%d%d", &s, &t, &m); while (m--) { scanf("%d%d%d", &a, &b, &c); if (a != b) { addedge(a, b, c, c); } } int ans = sap(s, t, n); printf("Case %d: %d\n", cases++, ans); } return 0; }