1.15 网络流、矩阵快速幂、DDP

网络流

网络流的问题套路性较强,需要多刷题。

模板

Dinic 算法求最大流

点击查看代码
ll n,m,s,t;
struct edge
{
	ll v,w,nxt;
}e[M*2];
ll head[N],tot=1;
void insert(ll u,ll v,ll w)
{
	tot++;
	e[tot].v=v;e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
}
ll dep[N],nt[N];
bool bfs()
{
	for(ll i=1;i<=n;i++)
		nt[i]=head[i],dep[i]=0;
	queue<ll> q;
	dep[s]=1;
	q.push(s);
	while(!q.empty())
	{
		ll c=q.front();
		q.pop();
		for(ll i=head[c];i;i=e[i].nxt)
		{
			if(e[i].w>0 && !dep[e[i].v])
			{
				dep[e[i].v]=dep[c]+1;
				q.push(e[i].v);
			}
		}
	}
	return dep[t];
}
ll dfs(ll x,ll cap)//return: real flow, cap: capacity
{
	if(x==t)
		return cap;
	ll sum=0;
	for(ll i=nt[x];i;nt[x]=i=e[i].nxt)
	{
		if(e[i].w && dep[x]+1==dep[e[i].v])
		{
			ll flow=dfs(e[i].v,min(cap-sum,e[i].w));
			e[i].w-=flow;
			e[i^1].w+=flow;
			sum+=flow;
			if(sum==cap)
				break;
		}
	}
	return sum;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>s>>t; 
	for(ll i=1;i<=m;i++)
	{
		ll u,v,w;
		cin>>u>>v>>w;
		insert(u,v,w);
		insert(v,u,0);
	}
	ll ans=0;
	while(bfs())
	{
		ll flow=dfs(s,inf);
		ans+=flow;
		if(!flow)
			break;
	}
	cout<<ans<<endl;
    return 0;
}

最大流

\(f(u,v)\) 表示 \((u,v)\) 的流量,\(c(u,v)\) 表示 \((u,v)\) 的容量,\(d(u,v)\)\((u,v)\) 的残量。\(d(u,v)=c(u,v)-f(u,v)\)

残量网络 \(G_d\) 是所有 \(d(u,v)>0\) 的边构成的图。残量网络上一条 s 到 t 的路径称为增广路,也就是可以再增加流量的路。

最大流最小割定理

对于一组图的分割 \({S,T}\),如果 \(s\in S,\ t\in T\),我们称这是一组图的 s-t 割。一组 s-t 割的跨越两个子集的边的容量称为这组 s-t 割的容量。

图的最大流小于等于任意 s-t 割的容量,等号成立当且仅当所有跨越子集的边都满流(反边都空流)。

感性理解是,网络的任何一个截面必须能容纳整个网络的流量。

最大流等于最小的 s-t 割的容量。

证明:在残量网络不存在从 s 到 t 的路径时,存在一个 s-t 割中每条边都没有残量。同时,对于一个流,显然任意一个割的流量大于这个流的流量。因此此时网络流量等于最大流,而网络流量等于最小割。

所以我们证明了,最大流等于最小割。

注意到,二分图匹配中的 Konig 定理也和这一定理有关。

Ford-Fulkerson 增广

贪心地寻找增广路的一类算法的总称。

对于每条边,建一条容量为 \(0\) 的反边,维护 \(f(u,v)=-f(v,u)\)。当我们增广一条路时,可以走过反边,相当于是撤回了这条边上的一些操作。

这里存在 \(f(v,u)\) 是负数是一件迷惑的事情,但是我们注意到 \(c(v,u)=0\),因此 \(d(v,u)\) 实际上是正的,而这是我们唯一关心的东西。

为什么要建反边?

因为贪心无法确定多条残量一样的流会对后面产生什么影响,这是类似反悔贪心的思想。

正确性证明依赖 最大流最小割定理。

在整数流量的网络 \(G = (V, E)\) 上,平凡地,我们假设每次增广的流量都是整数,则 Ford–Fulkerson 增广的时间复杂度的一个上界是 \(O(|E||f|\)),其中 \(f\)\(G\) 上的最大流。这是因为单轮增广的时间复杂度是 \(O(|E|)\),而增广会导致总流量增加,故增广轮数不可能超过 \(|f|\)

Dinic 算法是 Ford-Fulkerson 增广的一种优化子版本。

Dinic 算法

考虑在增广前先对 残量网络 \(G_d\) 做 BFS 分层,即根据结点 \(u\) 到源点 \(s\) 的距离 \(d(u)\) 把结点分成若干层。令经过 \(u\) 的流量只能流向下一层的结点 \(v\),即删除 \(u\) 向层数标号相等或更小的结点的出边,我们称 \(G_d\) 剩下的部分为层次图(Level Graph)。

如果我们在层次图 \(G_L\) 上找到一个最大的增广流 \(f_b\),使得仅在 \(G_L\) 上是不可能找出更大的增广流的,则我们称 \(f_b\)\(G_L\) 的阻塞流(Blocking Flow)。这里的 增广流 是指多条增广路并起来的流量。

定义层次图和阻塞流后,Dinic 算法的流程如下。

  • 在残量网络 \(G_d\) 上 BFS 出层次图 \(G_L\)
  • 在层次图 \(G_L\) 上 DFS 出阻塞流 \(f_b\)
  • 将新的增广流 \(f_b\) 并到原先的流 \(f\) 中,即 \(f \leftarrow f + f_b\)
  • 重复以上过程直到残量网络 \(G_d\) 上不存在从 \(s\)\(t\) 的路径。

此时的 \(f\) 即为最大流。

在求阻塞流的过程中,需要使用 当前弧优化
注意到在 \(G_L\) 上 DFS 的过程中,如果结点 \(u\) 同时具有大量入边和出边,并且 \(u\) 每次接受来自入边的流量时都遍历出边表来决定将流量传递给哪条出边,则 \(u\) 这个局部的时间复杂度最坏可达 \(O(|E|^2)\)。为避免这一缺陷,如果某一时刻我们已经知道边 \((u, v)\) 已经增广到极限(边 \((u, v)\) 已无剩余容量或 \(v\) 的后侧已增广至阻塞),则 \(u\) 的流量没有必要再尝试流向出边 \((u, v)\)。据此,对于每个结点 \(u\),我们维护 \(u\) 的出边表中第一条还有必要尝试的出边。习惯上,我们称维护的这个指针为当前弧,称这个做法为当前弧优化。

将单轮增广的时间复杂度 \(O(|V||E|)\) 与增广轮数 \(O(|V|)\) 相乘,Dinic 算法的时间复杂度是 \(O(|V|^2|E|)\)

最小费用最大流

定义费用 \(w(u,v)=-w(v,u)\),对于 \((u,v)\),付出 \(f(u,v)\times w(u,v)\) 的代价。求出费用最小的最大流。

如果没有负环,使用 SSP 算法:每次寻找单位费用最小的(\(w\) 总和最小的)增广路增广。如果有负环,就不行,得用Primal-Dual 原始对偶算法。

SSP算法的流程: 每次用 SPFA 或 dijkstra 求出到每个点的最小单位费用(最短路),然后在 dfs 的时候将原本判断深度的判定改成 \(dis_v=dis_u+c_{u,v}\) 即可。

正确性: 考虑我们在 dfs 中流过的一定是目前的最小费用阻塞流,然后我们有一个神奇结论————贪心地选取目前最小费用阻塞流不会对以后的产生负面影响(但我不会证明)。

可用于建模解决反悔贪心问题。

例题

最小割模型(一)

\(n\) 个物品和 \(2\) 个集合,如果一个物品没有放入 \(A\) 集合会花费\(a_i\),如果没有放入 \(B\) 集合会花费 \(b_i\)。还有限制 \(u_i\)\(v_i\) 物品如果放到一起会产生 \(w_i\) 的代价。

这是一个经典的 二者选其一 的最小割题目。我们对于每个集合设置源点 \(s\) 和汇点 \(t\),第 \(i\) 个点由 \(s\) 连一条容量为 \(a_i\) 的边、向 \(t\) 连一条容量为 \(b_i\) 的边。对于限制条件 \(u,v,w\),我们在 \(u,v\) 之间连容量为 \(w\) 的双向边。

最小割等于最大流即可。

割边数量

如果需要在最小割的前提下最小化割边数量,那么先求出最小割,把没有满流的边容量改成 \(\infty\),满流的边容量改成 \(1\),重新跑一遍最小割就可求出最小割边数量;如果没有最小割的前提,直接把所有边的容量设成 \(1\),求一遍最小割就好了。


很大一部分内容引用自 OI-wiki,遵循CC BY-SA 4.0


矩阵快速幂

性质(广义矩阵乘法)

矩阵乘法具有结合律和分配律,但不具有交换律。矩阵快速幂依赖于矩阵乘法的结合律。

矩阵乘法的结合律依赖于乘法在加法上的分配律、乘法结合律、加法结合律、加法交换律。因此如果要以别的二元运算代替乘法和加法,必须也具有以上性质。

证明见 HTC 的博客

这里写一点补充吧:乘法在加法上的交换律是为了把系数放进/放出 \(\sum\) 号;乘法结合律是为了拆括号;加法结合律和交换律是因为中途要交换两个 \(\sum\) 号(htc写的有点问题)。

一个典型的例子:用 \(\min\) 代替加法,用加法代替乘法,便可以求出恰好经过 \(k\) 条边的最短路。

动态DP(DDP)

狭义来说解决树上带修DP问题,广义来说解决大部分带修DP。

用线段树之类的数据结构储存、维护动态规划在每个位置上的的转移矩阵(广义矩阵,如max/+矩阵)。用于原本可以DP的问题的带修版本。

posted @ 2025-02-20 22:38  Luke_li  阅读(28)  评论(0)    收藏  举报