加载中...

网络流

概念

pmB94S0.png

参考博客

blog1

可视化

video1

最大流

EK 算法

理论上限复杂度 \(O(nm^{2})\)

const int N = 205;
const int M = 5005;


struct Edge{
    ll v, c; // v 代表有向边指向的点
    int ne; // ne 代表邻接表中下一条边的编号
}e[M<<1];
int head[N], idx=1; // 边从(2,3)开始编号
ll minf[N]; // minf[u]:当前增广路到达 u 经过的实际流量(取前面经过所有边剩余容量的最小值)
int pre[N]; // pre[u]:在增广路中 u 的回溯边编号,用于构建残量网络
int n, m, S, T;

void addedge(int u, int v, int c){ // (u, v) 间添加容量为 c 的有向边
    e[++idx] = {v, c, head[u]};
    head[u] = idx;
}
bool bfs(){ // 返回值表示是否找到一条增广路
    memset(minf, 0, sizeof minf);
    queue<int> q;
    q.push(S);
    minf[S] = INF;
    while(q.size()){
        int u = q.front();
        q.pop();
        for(int i = head[u]; i; i = e[i].ne){
            int v = e[i].v;
            if(minf[v] == 0 && e[i].c > 0){ // 当前未访问过,且剩余容量 > 0
                minf[v] = min(minf[u], e[i].c);
                pre[v] = i;
                q.push(v);
                if(v == T) return true;
            }
        }
    }
    return false;
}

ll EK(){
    ll flow = 0;
    while(bfs()){ // 不断地 
        int v = T;
        while(v != S){
            int i = pre[v];
            e[i].c -= minf[T];
            e[i^1].c += minf[T];
            v = e[i^1].v;
        }
        flow += minf[T];
    }
    return flow;
}

void solve()
{
    cin >> n >> m >> S >> T;
    for(int i = 1; i <= m; i ++){
        int u, v, c;
        cin >> u >> v >> c;
        addedge(u, v, c);
        addedge(v, u, 0);
    }
    cout << EK() << "\n";
}

Dinic 算法

struct Edge{
    ll v, c; // v 代表有向边指向的点
    int ne; // ne 代表邻接表中下一条边的编号
}e[M<<1];
int head[N], idx=1; // 边从(2,3)开始编号
int d[N], cur[N];
int S, T;

void addedge(int u, int v, int c){ // (u, v) 间添加容量为 c 的有向边
    e[++idx] = {v, c, head[u]};
    head[u] = idx;
}
bool bfs(){ // 对所有点分层,找增广路
    memset(d, 0, sizeof d);
    queue<int> q;
    q.push(S);
    d[S] = 1;
    while(q.size()){
        int u = q.front();
        q.pop();
        for(int i = head[u]; i; i = e[i].ne){
            int v = e[i].v;
            if(d[v] == 0 && e[i].c > 0){ // 当前未访问过,且剩余容量 > 0
                d[v] = d[u] + 1;
                q.push(v);
                if(v == T) return true;
            }
        }
    }
    return false;
}

ll dfs(int u, ll minf){ // 多路增广
    if(u == T) return minf;
    ll sum = 0;
    for(int i = cur[u]; i; i = e[i].ne){
        cur[u] = i; // 当前弧优化
        int v = e[i].v;
        if(d[v] == d[u] + 1 && e[i].c > 0){
            ll f = dfs(v, min(minf, e[i].c));
            e[i].c -= f;
            e[i^1].c += f;
            sum += f;
            minf -= f;
            if(minf == 0) break; // 剩余流量优化
        }
    }
    if(sum == 0) d[u] = 0; // 残枝优化
    return sum;
}

ll dinic(){
    ll flow = 0;
    while(bfs()){
        memcpy(cur, head, sizeof head);
        flow += dfs(S, INF);
    }
    return flow;
}

void solve()
{
    cin >> n >> m >> S >> T;
    for(int i = 1; i <= m; i ++){
        int u, v, c;
        cin >> u >> v >> c;
        addedge(u, v, c);
        addedge(v, u, 0); // !别忘了加反向边
    }
    cout << dinic() << "\n";
}

最小割

pmB2ihq.png

// 执行 dinic 算法后调用
bool vis[N];
void mincut(int u){ // 最终根据 vis 值将点集划分成两类,即最小割
    vis[u] = 1;
    for(int i = head[u]; i; i = e[i].ne){
        int v = e[i].v;
        if(!vis[v] && e[i].c > 0) mincut(v);
    }
}

费用流

pmBWmk9.png

const int N = 5005;
const int M = 50005;

struct Edge{
    int v;
    int c; // 单位容量
    int w; // 单位流量费用
    int ne;
}e[M<<1];
int head[N], idx=1;
int d[N]; // 记录 spfa 最短路
int minf[N];
int pre[N];
bool st[N]; // spfa 标记数组
int flow, cost;

int n, m, S, T;

void addedge(int u, int v, int c, int w){
    e[++idx] = {v, c, w, head[u]};
    head[u] = idx;
}

bool spfa(){ // 每次从源点出发,找到达汇点 T 单位费用最小的增广路
    memset(d, 0x3f, sizeof d);
    memset(minf, 0, sizeof minf);
    queue<int> q;
    q.push(S);
    d[S] = 0;
    minf[S] = inf;
    st[S] = true;
    while(q.size()){
        int u = q.front();
        q.pop();
        st[u] = false;
        for(int i = head[u]; i; i = e[i].ne){
            int v = e[i].v, w = e[i].w;
            if(d[v] > d[u] + w && e[i].c > 0){
                d[v] = d[u] + w;
                minf[v] = min(minf[u], e[i].c);
                pre[v] = i;
                if(!st[v]){
                    st[v] = true;
                    q.push(v);
                }
            }
        }
    }
    return minf[T] > 0;
}
void EK(){
    while(spfa()){
        int v = T;
        while(v != S){
            int i = pre[v];
            e[i].c -= minf[T];
            e[i^1].c += minf[T];
            v = e[i^1].v;
        }
        flow += minf[T];
        cost += minf[T] * d[T];
    }
}

例题

还是那句话:网络流最重要的是如何把问题用图论建模,不要过多关注板子原理。

P2763

Q:\(n\) 份试题,\(k\) 种类型,每份试题可以属于多种类型中的某一种。现要求对于 \(\forall\) 类型 \(i\),恰好有 \(c_{i}\) 份试题属于类型 \(i\),给出一种构造方案(每种类型对应哪些试题)。

题解首页

code

P3254

Q:\(m\) 个不同单位的代表参加会议,其中第 \(i\) 个单位派出了 \(r_{i}\) 个代表。会议餐厅共有 \(n\) 张餐桌,第 \(i\) 张餐桌可容纳 \(c_{i}\) 位代表就餐。要求同一个单位的代表不在同一张餐桌就餐,给出一个满足要求的代表就餐方案。

和上一题思路一致。

code

P5014

\(n\) 件工作要分配给 \(n\) 个人做。第 \(i\) 个人做第 \(j\) 件工作产生的效益为 \(c_{i,j}\)。试设计一个将 \(n\) 件工作分配给 \(n\) 个人做的分配方案,使产生的总效益最小或最大。

显然是求 最小费用最大流 与 最大费用最大流。

code

P4016

Q:公司有 \(n\) 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 \(n\) 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运,保证货物总量为 \(n\) 的倍数。

pmsIhUf.png

code

code

code

code

posted @ 2026-08-04 09:59  小橘奏  阅读(4)  评论(0)    收藏  举报