网络流学习笔记
\(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\),最后求最大匹配就行了。

浙公网安备 33010602011771号