網路流 Dinic + SAP(模版)
阿新 • • 發佈:2019-02-07
參考博文
參考博文
只列出幾種常見的,快的網路流演算法
1.Dinic
Dinic利用dis陣列進行分層,每次流的時候都只流到下一層,大大減少時間複雜度
struct Edge{
int v, w, nxt;
};
Edge edge[maxn << 1];
int pre[maxn], tot, dis[maxn << 1], cur[maxn];
void init() {
tot = 0;
memset(edge, 0, sizeof(edge));
memset(pre, -1, sizeof(pre));
}
void addEdge(int s, int e, int w) {
edge[tot].v = e;
edge[tot].w = w;
edge[tot].nxt = pre[s];
pre[s] = tot ++;
}
int n, m, s, e;
int bfs() {
memset(dis, -1, sizeof(dis));
dis[e] = 0;
queue<int> que;
que.push(e);
while(!que.empty()) {
int u = que.front(); que.pop();
for (int i = pre[u]; i + 1; i = edge[i].nxt) {
if(dis[edge[i].v] == -1 && edge[i ^ 1].w > 0) {
dis[edge[i].v] = dis[u] + 1;
que.push(edge[i].v);
}
}
}
return dis[s] != -1;
}
int dfs(int u, int flow) {
if(u == e) return flow;
int belta = flow;//belta 可以看作是從源點到此處的最大容量
for (int &i = cur[u]; i + 1; i = edge[i].nxt) {
//cur[i] = i;
if(dis[u] == dis[edge[i].v] + 1 && edge[i].w > 0) {
int d = dfs(edge[i].v, min(belta, edge[i].w));
edge[i].w -= d; edge[i ^ 1].w += d;
belta -= d;
if(belta == 0) break;//如果容量為0,那麼這條路就不再有增廣路,break
}
}
return flow - belta;//返回的是 最大容量-剩餘容量
}
int dinic() {
int ans = 0;
while(bfs()) {
for (int i = 1; i <= n; i ++)
cur[i] = pre[i];
ans += dfs(s, INF);
}
return ans;
}
int main(int argc, const char * argv[]) {
int T, karse = 0;
scanf("%d", &T);
while(T --) {
init();
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i ++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, 0);
}
s = 1; e = n;
printf("Case %d: %d\n", ++karse, dinic());
}
其實SAP與Dinic本質都是一樣的,都是對圖進行分層,不過Dinic是每跑一次都對dis[]重新編號,而SAP用gap陣列來記錄距離匯點的距離,如果某一個距離為0,那麼這個圖就不再有增廣路。
如圖:
初始的分層是這樣的,1是源點,6是匯點
經過一次查詢後
而此時2與3聯通,但是不滿足dis[2] == dis[3] + 1。這時
而此時1又與2的dis相同,所以1也+1
其餘的與這個類似
2.SAP
SAP主函式
int tot, dis[maxn], head[maxn], aug[maxn], cur[maxn], flag[maxn], pre[maxn], S, T;
int SAP(int n){
int max_flow = 0,u = S, v;
int id, mindep;
aug[S] = INF;
pre[S] = -1;//S的父節點為-1S
memset(dis, 0, sizeof(dis));
memset(gap, 0, sizeof(gap));
gap[0] = n;//初始位置為0的有n個
for(int i = 0; i <= n; i ++)
cur[i] = head[i]; // 初始化當前弧為第一條弧
while(dis[S] < n){ //當dis[S]< n時
int flag = 0;
if(u == T){
max_flow += aug[T];
for(v= pre[T]; v != -1; v = pre[v]){ // 路徑回溯更新殘留網路
id = cur[v];//v是T的前繼,id是前繼在edge裡的編號
edge[id].w -= aug[T];
edge[id^1].w += aug[T];
aug[v] -= aug[T]; // 修改可增廣量,以後會用到
if(edge[id].w == 0) // 不回退到源點,僅回退到容量為0的弧的弧尾
u = v;
}
}
for(int i = cur[u]; i != -1; i = edge[i].nxt){
v = edge[i].v; // 從當前弧開始查詢允許弧
if(edge[i].w > 0 && dis[u] == dis[v] + 1){ // 找到允許弧
flag = 1;
pre[v] = u;
cur[u] = i;//記錄u這個點的邊的編號
aug[v] = min(aug[u], edge[i].w);//更新aug[v]
u = v;//u向下
break;
}
}
if(!flag){//沒找到弧
if(--gap[dis[u]] == 0) /* gap優化,層次樹出現斷層則結束演算法 */
break;
mindep = n;
cur[u] = head[u];
for(int i = head[u]; i != -1; i = edge[i].nxt){
v = edge[i].v;
if(edge[i].w > 0 && dis[v] < mindep){
mindep = dis[v];
cur[u] = i; // 修改標號的同時修改當前弧
}
}
dis[u] = mindep + 1;
gap[dis[u]] ++;
if(u != S) // 回溯繼續尋找允許弧
u = pre[u];
}
}
return max_flow;
}
main() {
SAP(n + 1);//傳進去的引數是總點數的大小,因為有n個點就可以分為n-1層(反正開大點肯定沒錯_-_)
}