Loading

[笔记] 网络流

该算法的一大判断依据,DP 不可做 or 状态高,数据范围适合网络流。

最大流

Dinic 的实现

int S, T, ecnt = 1, head[N], cur[N], dep[N];
struct edge{ int nx, to, v; } e[M * 2];
inline void add(int u, int v, int w){ e[++ecnt] = {head[u], v, w}, head[u] = ecnt; }
inline void Add(int u, int v, int w){ add(u, v, w), add(v, u, 0); }
bool bfs(){
	static queue <int> Q;
	lfor(i, 1, T) dep[i] = T + 1;
	Q.push(S), dep[S] = 1;
	while(!Q.empty()){
		int x = Q.front(); Q.pop();
		for(int i = head[x]; i; i = e[i].nx){
			int y = e[i].to;
			if(dep[y] > dep[x] + 1 && e[i].v) dep[y] = dep[x] + 1, Q.push(y);
		}
	}
	return dep[T] != T + 1;
}
int dfs(int x, int res){
	if(!res || x == T) return res;
	int sum = 0;
	for(int &i = cur[x]; i; i = e[i].nx){
		int y = e[i].to;
		if(dep[y] == dep[x] + 1 && e[i].v){
			int get = dfs(y, min(e[i].v, res));
			sum += get, res -= get;
			e[i ^ 1].v += get, e[i].v -= get;
		}
   if(!res) break;
	}
	return sum;
}
inline int Dinic(){
	int sum = 0;
	while(bfs()) memcpy(cur, head, sizeof cur), sum += dfs(S, INF);
	return sum;
}
时间复杂度

\(O(n^2m)\)

  • 特殊的,在边权为 \(1\) 的时候,时间复杂度为 \(O(n\sqrt m)\),故二分图最大匹配题目可以出到 \(10^5\) 级别。

费用流

SSP 的实现

inline void add(int u, int v, int w, int c){
	e[++cnt] = (edge){head[u], v, c, w}; head[u] = cnt;
	e[++cnt] = (edge){head[v], u, -c, 0}; head[v] = cnt;
}
inline bool spfa(){
	for(Ri int i(1); i <= n; ++i) dis[i] = INF, flo[i] = 0;
	dis[st] = 0, flo[st] = INF, inq[st] = 1, q[h = t = 1] = st;
	while(h <= t){
		int x = q[h++]; inq[x] = 0;
		for(Ri int i(head[x]), y; i; i = e[i].nx){
			y = e[i].to;
			if(dis[y] > dis[x] + e[i].v && e[i].f){
				pre[y] = i;
				dis[y] = dis[x] + e[i].v, flo[y] = min(flo[x], e[i].f);
				if(!inq[y]) q[++t] = y, inq[y] = 1;
			}
		}
	}
	return dis[ed] != INF;
}
void SSP(int &Mf, int &Mc){
	while(spfa()){
		Mf += flo[ed], Mc += flo[ed] * dis[ed];
		int now = ed;
		while(pre[now]){
			now = pre[now];
			e[now].f -= flo[ed], e[now ^ 1].f += flo[ed];
			now = e[now ^ 1].to;
		}
	}
}

有上下界可行流

\((u,v,l,r)\) 表示 \(u\)\(v\) 的一条边流量上下界分别为 \(l,r\)

考虑新建虚拟源汇 \(SS,TT\),记 \(d_u\) 表示所有流入 \(u\) 的流量减流出 \(u\) 的流量 (此处流量按照边的下界).

对于 \((u,v,l,r)\)\(u\)\(v\) 连流量为 \(r-l\) 的边。

对于 \(d_u>0\)\(SS\)\(u\) 连流量为 \(d_u\) 的边。

对于 \(d_u<0\)\(u\)\(TT\) 连流量为 \(-d_u\) 的边。

最终如果 \(SS\) 连出的边满流即有解。

拓展

  • 有源汇:\(T\rightarrow S\) 一条流量为 \(\infty\) 的边,注意答案流量应取加的这条边的反向边的流量。
  • 有源汇最大流:删去所有附加边后,跑 \(S\)\(T\) 的最大流,相加。
  • 有源汇最小流:删去所有附加边后,跑 \(T\)\(S\) 的最大流,相减。
  • 有源汇费用流 (此时的流量仅保证是可行流):直接选择费用流即可。

代码 (有源汇最大流):

lfor(i, 1, tot){
		int u = E[i].u, v = E[i].v, l = E[i].l, r = E[i].r;
		Add(u, v, r - l), d[v] += l, d[u] -= l;
	}

	int tmp = ecnt, sum = 0; SS = T + 1, TT = T + 2;
	Add(T, S, INF);
	lfor(i, 1, T){
		if(d[i] > 0) Add(SS, i, d[i]), sum += d[i];
		if(d[i] < 0) Add(i, TT, -d[i]);
	}
	
	if(Dinic(SS, TT) != sum) return puts("No"), 0;
	int res = e[tmp + 2].v;
	lfor(i, tmp + 1, ecnt) e[i].v = 0;
	printf("%d\n", res + Dinic(S, T));
posted @ 2022-02-16 10:42  IrisT  阅读(30)  评论(0编辑  收藏  举报