「学习笔记」网络流基础
Dinic当前弧优化
其实和求哈密尔顿回路的一个算法的思想是一样的
代码如下:
inline bool bfs(){
for(int i=1;i<=n+4;++i) dep[i]=0,cur[i]=head[i]; dep[Ts]=1; q.push(Ts);
while(q.size()){
int fr=q.front(); q.pop();
for(int i=head[fr];i;i=e[i].nxt){
int t=e[i].to; if(!e[i].lim||dep[t]) continue;
dep[t]=dep[fr]+1; q.push(t);
}
}return dep[Tt];
}
inline int dfs(int x,int in){
if(x==Tt) return in; int out=0;
for(int i=cur[x];i&∈cur[x]=i,i=e[i].nxt){
int t=e[i].to; if(dep[x]!=dep[t]-1||!e[i].lim) continue;
int res=dfs(t,min(in,e[i].lim)); out+=res; in-=res; e[i^1].lim+=res; e[i].lim-=res;
} if(!out) dep[x]=0; return out;
}
最大权闭合子图
如果选定一个点则后面的点都选,那么称这部分是闭合子图
每个点有点权的话,最大权闭合子图顾名思义即可
考虑如何用网络流建图解决这个问题:
原点向正权点建流量为 \(v_i\) 的边,负权点向汇点建流量为 \(-v_i\) 的边,原图中间的边都是 \(inf\) 的流量,答案为 正权点的权值和减掉最小割
考虑证明:
\((1)\) 闭合子图,如果选择割掉一个边,那么其后继必然被割掉,否则还是联通的
\((2)\) 最大权:最小割为没有割掉的正权点和割掉的负权点,所以得证
例题是太空飞行计划问题
上下界网络流
对于每个边,有 \(L_i\) 表示最小流量,\(R_i\) 表示最大流量
无源汇上下界可行流
这东西其实也叫循环流,顾名思义
注意,这里不是最大流,只是可行流
问题出在每个点可能不满足流量守恒的原则
那么考虑构造流量守恒,在 \(R_i-L_i\) 作为 \(lim_i\) 的残量网络上跑最大流
为了解决这个不守恒的问题,考虑维护出来一个 \(A[i]\) 表示 \(i\) 的流入减去流出
为了让这些过程或者不足的东西有所归属,那么再来个源汇点进行提供和接受
建立新源/汇点 解决流量不守恒的问题确实是非常好的手段
那么具体建边也呼之欲出了:
for(int i=1;i<=m;++i){
int u=read(),v=read(),l=read(),r=read();
if(r-l) add(u,v,r-l,i); //这里的 i 是辅助最后求每个边的流量的
a[u]-=l; a[v]+=l; mn[i]=l;
}
for(int i=1;i<=n;++i){
if(a[i]<0) add(i,T,-a[i],0);
else sum+=a[i],add(S,i,a[i],0);
}
跑一下最大流,每个边的流量就是对应 \(id=i\) 的反向边的流量加上流量下界
有源汇上下界可行流
与上面不同的是,这里的 \(S,T\) 不需要满足流量守恒
不守恒考虑构造成守恒,连一个 \((T\to S,0,inf)\) 的边即可
又变成了循环流,求流量具体值很简单,直接去连的边的反向边流量即可
有源汇上下界最大可行流
把可行流扔掉继续跑就行了
有源汇上下界最小流
跑完可行流之后考虑反向边的问题
在反向边上跑等价于减小流量,那么剪掉 \(T\to S\) 在残量网络上的最大流即可
例题:清理雪道(真-模板题)
有些细节上的问题:
-
初始的 \(dinic\) 取可行流在 \(e[cnt].lim\) 中做,每次叠加 \(dfs\) 的返回值不对
-
第二次做 \(\texttt{T->S}\) 的最大流注意把原来 \(S\to T\) 的无穷边删掉
有源汇上下界费用流
考虑这个费用流的定义并不需要满足是最大流
所以在残量网络中按照和可行流中一样的构造数组 \(A[i]\)
对于新源点汇点连入和连出的边费用都是 \(0\)
剩下的就是一个最小费用最大流
例题
植物大战僵尸
貌似是显然的最大权闭合子图的模型
考虑每个图的子图本质上是能攻击到它的所有点和所有它前面的点
朴素的网络流图应该是:\(S\) 向每个点连边,然后每个点向列数大于它的点连,被攻击的连一个指向攻击的,\(col=m-1\) 的所有点连向 \(T\)
那么跑最大权闭合子图即可
然后完蛋了,因为还得拓扑跑一个无敌点……
魔术球问题
网络流二十四题中的输出方案一般都是考虑流量的问题即可
因为每个点只能和标号小于其的点连,那么每次上来枚举 \(i^2\in [now+1,2\times now]\)
拆点之后考虑最大流是不是更新,没有更新说明没有更优的选择来容纳这个点,开新柱子即可
寿司餐厅
本来以为可以多次累计,发现不能之后意识到了是个最大权闭合子图的模型
对每个区间 \([l,r]\) 建立一个点来表示之,那么闭合子图在这里的定义所有的子区间
那么对于 \(d\) 的连边不妨考虑 \([l,r]\to [l,r-1],[l+1,r]\)
如果这么着就做完了,就不是省选题目了,所以出现了费用
那么思考费用的实际含义:选择 \(i\) 则付出 \(a_i\) 的代价,选择过 \(a_i\) 则要付出 \(m a_i^2\) 的代价
这样每个最终的区间 \([i,i]\) 往对应的类型建边即可
矩阵
二分答案,那么每个位置就成了 \([a_{i,j}-mid,a_{i,j}+mid]\) 和 \([L,R]\) 取交集的上下界
所以一定要看对题,是求和不超过 \(mid\),那么建出来以下三类:
用上下界改一下图,判断是不是存在可行流即可
支线剧情
简单建图之后套最小费用可行流即可
注意:\(\texttt{spfa}\) 中的更新应该是 \(\texttt{d[t]>d[fr]+e[i].cst}\) ,如果等于不能更新
志愿者招募
对于区间操作,连一个 \(\texttt{(id[s],id[t+1])}\) 的边来维护差分
接着套路式地把两天之间连上之后来继承上一天的信息,费用自然是 \(0\),\(\texttt{(S,id[1]),(id[n+1],T)}\) 也得连上
那么显然的应该把每个志愿者的费用当成每条边的费用来跑最小费用最大流
最后的问题就是如何处理图上的流量
因为 \(a[i]\) 不好处理,那么考虑 “补流”,也就是说把每个点最后补上 \(a_i\) 的流量
那么天之间的流量是 \(\texttt{inf-a[i]}\) 剩下的是 \(\texttt{inf}\) 即可
旅行时的困惑
有源汇上下界最小流套模型即可