网络流学习笔记

\(Dinic\) 算法

朴素的增广路就是直接 dfs 找,记得建反边,每经过一条边,这条边的容量减去经过的流量,他的反边加上经过的流量。

然后有三步

  • 第一步是在找增广路前 bfs 一遍,求出每个点到源点的距离,在找增广路时只前往距离比自己多 \(1\) 的点,很简单。
  • 第二步是不很粗暴的一直 dfs,而是记录一个 \(used\),记录从当前点出发到汇点,有多少流量通过,但是从源点到当前点,也经过了一些路径,这些路径也有一个最大流量,所以当 \(used\) 达到了这个最大流量时,就返回。
  • 第三步是弧优化,只需要多一个 \(now\) 数组,在每次枚举一个点的边时从记录的上一个 \(now\) 开始即可。记得在每次 bfs 完之后把 \(now\) 数组清空。
Show Code

#include
#define fi first
#define se second
#define mp make_pair
#define int long long
using namespace std;
auto mread = [](){
	int x;
	scanf("%lld", &x);
	return x;
};
const int N = 205;
int n = mread(), m = mread(), s = mread(), t = mread(), d[N], ans, now[N];
vector > v[N];
vector belong[N];
bool bfs(){
	memset(d, 0x3f, sizeof(d));
	memset(now, 0, sizeof(now));
	d[s] = 0;
	queue q;
	q.push(s);
	int e = 0;
	while(q.size()){
		int x = q.front();
		q.pop();
		if(x == t)
		e = 1;
		for(int i = 0; i < v[x].size(); i ++){
			int y = v[x][i].fi;
			if(d[y] > d[x] + 1 && v[x][i].se){
				d[y] = d[x] + 1;
				q.push(y);
			}
		}
	}
	return e;
}
int dfs(int x, int flow){
	if(x == t)
	return flow;
	int used = 0;
	for(int i = now[x]; i < v[x].size(); i ++){
		now[x] = i;
		int y = v[x][i].fi;
		if(v[x][i].se && d[y] == d[x] + 1){
			int vlow = dfs(y, min(flow - used, v[x][i].se));
			if(vlow){
				used += vlow;
				v[x][i].se -= vlow;
				v[y][belong[x][i]].se += vlow;
				if(used == flow)
				break;
			}
		}
	}
	return used;
}
signed main(){
	for(int i = 1, x, y, z; i <= m; i ++){
		x = mread(), y = mread(), z = mread();
		belong[x].push_back(v[y].size());
		belong[y].push_back(v[x].size());
		v[x].push_back(mp(y, z));
		v[y].push_back(mp(x, 0));
	}
	while(bfs())
	ans += dfs(s, LONG_LONG_MAX);
	printf("%lld", ans);
	return 0;
}

\(Dinic\) 费用流

普通的 Dinic 是 bfs 然后分层,费用流只需要将 bfs 改成基于费用的 spfa 即可,实现时注意以下几点:

  • 建反边时费用是正边的相反数。
  • dfs 时记得记录一个标记数组 p[],在一个点向外 dfs 时,这个点标记上,枚举完所有边后再去掉标记,向一个点 dfs 时,这个点必须是没有标记上的。
  • 最终费用可以用一个全局变量存下来,每次向一个点 dfs 时,就让费用加上单位费用乘 dfs 过去的流量。
Show Code

#include
#define int long long
#define fi first
#define se second
#define mp make_pair
using namespace std;
auto mread = [](){int x; scanf("%lld", &x); return x;};
const int N = 6005, INF = 0x3f3f3f3f;
struct node{
    int v, w, c;
};
vector v[N];
vector belong[N];
int n = mread(), k = mread(), a[55][55], now[N], dis[N], p[N], ans, cost;
void add(int x, int y, int w, int c){
    belong[x].push_back(v[y].size());
    belong[y].push_back(v[x].size());
    v[x].push_back((node){y, w, c});
    v[y].push_back((node){x, 0, -c});
}
bool bfs(int s, int t){
    memset(now, 0, sizeof(now));
    memset(dis, 0x3f, sizeof(dis));
    queue q;
    dis[s] = 0;
    q.push(s);
    while(q.size()){
        int x = q.front();
        q.pop();
        p[x] = 0;
        for(int i = 0; i < v[x].size(); i ++){
            if(!v[x][i].w)
            continue;
            int y = v[x][i].v, w = v[x][i].c;
            if(dis[x] + w < dis[y]){
                dis[y] = dis[x] + w;
                if(p[y] == 0)
                q.push(y), p[y] = 1;
            }
        }
    }
    return dis[t] < INF;
}
int dfs(int s, int t, int x, int flow){
    if(x == t)
    return flow;
    int used = 0;
    p[x] = 1;
    for(int i = now[x]; i < v[x].size(); i ++){
        now[x] = i;
        int y = v[x][i].v;
        if(!p[y] && dis[x] + v[x][i].c == dis[y] && v[x][i].w){
            int vlow = dfs(s, t, y, min(v[x][i].w, flow - used));
            if(vlow){
                v[x][i].w -= vlow;
                cost += v[x][i].c * vlow;
                v[y][belong[x][i]].w += vlow;
                used += vlow;
                if(used == flow)
                break;
            }
        }
    }
    p[x] = 0;
	return used;
}
signed main(){
    add(0, 1, k, 0);
    int snow = 0;
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= n; j ++){
            a[i][j] = mread();
            snow ++;
            add(snow, snow + 3000, 1, -a[i][j]);
            add(snow, snow + 3000, LONG_LONG_MAX, 0);
            if(j < n)
            add(snow + 3000, snow + 1, LONG_LONG_MAX, 0);
            if(i < n)
            add(snow + 3000, snow + n, LONG_LONG_MAX, 0);
        }
    }
    while(bfs(0, snow + 3000)){
        int x;
        while(x = dfs(0, snow + 3000, 0, LONG_LONG_MAX)){
            ans += x;
        }
    }
    printf("%lld\n", -cost);
    return 0;
}

最小路径覆盖

在一张 DAG 上,如果我们要求用最少条不相交的链覆盖整个图(一个点也算一条链),可以转化为二分图问题,并且使用网络流求解。

假设我们把最终的图提取出来,也就是这些链,那么显然每个点的入度和出度都最多是 \(1\),所以我们可以给每个点都选择一个出点,只要一个点选择了一个出点,那么链的个数就会减少 \(1\),我们把一个点 \(i\) 连向一个点 \(j\),看作把点 \(i\) 向点 \(j\) 匹配,所以我们要求最大匹配数。由于 \(i\) 连想 \(j\) 后,\(j\) 仍然可以连向别的点,所以我们把点复制一份,每次都是 \(i\) 连向 \(j + n\),最后求最大匹配就行了。

posted @ 2024-01-20 16:18  cndark_moon  阅读(33)  评论(0)    收藏  举报