1. 程式人生 > >網路流 Dinic + SAP(模版)

網路流 Dinic + SAP(模版)

參考博文

參考博文

只列出幾種常見的,快的網路流演算法

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層(反正開大點肯定沒錯_-_)
}