网络流相关
[2025-04-01] 增加了 P2053,P3327 等题目。
[2025-04-02] 完善了 P6220,增加了 P4298。
[2025-04-10] 增加了 P2770 等一类最大路径权值和(方格取数)问题和 P2604。
网络最大流
最大流 Dinic 板子:
bool bfs(){
memset(dep,0,sizeof(dep));
while(!q.empty()) q.pop();
q.push(s); dep[s]=1; nw[s]=hd[s];
while(!q.empty()){
int x=q.front(); q.pop();
for(int i=hd[x];i;i=eg[i].nxt){
int y=eg[i].v;
if(dep[y]||!eg[i].w) continue;
q.push(y); nw[y]=hd[y]; dep[y]=dep[x]+1;
if(y==t) return true;
}
}
return false;
}
int dfs(int x,int flow){
if(x==t) return flow;
int rst=flow,i,tmp;
for(i=nw[x];i&&rst;i=eg[i].nxt){
int y=eg[i].v; nw[x]=i; //注意当前弧优化!
if(!eg[i].w||dep[y]!=dep[x]+1) continue;
tmp=dfs(y,min(rst,eg[i].w));
if(!tmp) dep[y]=0;
eg[i].w-=tmp; eg[i^1].w+=tmp; rst-=tmp;
}
return flow-rst;
}
int dinic(){
int mxflow=0,flow;
while(bfs())
while(flow=dfs(s,inf)) mxflow+=flow;
return mxflow;
}
首先一遍 dp 求出 \(f_i\) 表示以第 \(i\) 个数结尾的最长不降子序列的长度。
容易发现,最终若取出 \(i\),则 \(i\) 一定在子序列的第 \(f_i\) 个,考虑每个点 \(i\) 向 \(f_j=f_i+1\) 的 \(j\) 连边。为了保证每个点只选一次,拆点,入点出点间流量为 \(1\)。
第三问不限制 \(1\) 和 \(n\) 经过的次数,因此它们入点出点间的流量设为 \(\infty\)。
容易想到太空站为点,太空船为边。但是直接连难以刻画时间和周期。
考虑每个时刻对每个太空站新建结点,上一个时刻的太空站向下一个时刻的下一站连流量为太空船容量的边。枚举时刻跑最大流,直到地球到月球的流量 \(\geq\) 人数。
稍微想一下发现答案是 流量最大的边的流量 \(\times P\),而 Alice 希望答案最小,也就是流量最大的边的流量最小。
最大值最小考虑二分,限制每条边的流量 \(\leq mid\) 看最大流是否等于原图最大流即可。注意为了平均流量,流量可能是实数。
最小割
最大流 = 最小割
经典拆点转化。
考虑转化为最小割模型,由于最小割要求割边,而本题是割点,考虑将点转化为边,也就是将点拆为入点和出点,所有入点向出点连边,所有的边 \((x,y)\) 从 \(x\) 的出点连向 \(y\) 的入点,注意是双向边,然后求从 \(c1\) 的出点到 \(c2\) 的入点的最小割。
考虑 \(n\) 个点表示小朋友。若小朋友睡觉则连源点,不睡觉则连汇点。
现在要求矛盾会让源点汇点连通。对于有朋友关系的小朋友之间连边,显然若小朋友意见不同就会让源汇连通,此时可以发生冲突(割掉小朋友之间的边)或一个小朋友改变选择(割掉他和源/汇)的连边。
注意小朋友之间的连边要连双向边,因为可能是 \(A\) 让 \(B\) 和它一样选源点或是反过来,且这两种方案是不同的。可以发现双向边只会被割一条。
实际上是弱化版的集合划分模型。
看题面是把糕切开,结合数据范围容易想到最小割。
不加限制时,容易建图,可以看成是 \(P\times Q\) 条链连接源汇点(一个杏仁?),然后答案就是最小割。
考虑限制,需要让任意两条链 \(A,B\),若它们割的边相距 \(>D\),则源汇点会连通,这也是容易连的。
现在的问题就是为什么这样连边是对的,也就是不会出现一条链割两条边的最小割。这一部分在下一题可以很好地体现。
上一题的加强版,区别是差可能是负数,此时若不作处理会出现一条链割了两条边。
这题有两种连边方式,先讲第一种作为补救措施的连边。考虑加入链相反方向的 \(\infty\) 边,此时若一个链割了两条边,根据最小割的性质(若割了 \(u,v\),那么 \(u\) 与 \(S\),\(v\) 与 \(T\) 连通),这样一定不合法。
第二种是更加优雅地补全。实际上这题的连边已经可以刻画限制的传递,是比较完备的。注意到 \(k>0\) 时第一种做法反 \(\infty\) 边的作用是将前面的限制传给后面,但正常情况下后面的限制应当比前面更严格,补救边需要用当且仅当后面一些点因为不合法而没有被连上边,那么把这些边补上即可,\(k<0\) 应该差不多。
上一题为什么不需要这样的边呢?因为上一题绝对值的限制导致了不会出现不合法点,所以直接跑最小割就合法了。
集合划分模型
形如有一些物品,将其划分到 \(A,B\) 两个集合,其中物品 \(i\) 划分到 \(A,B\) 集合分别有 \(a_i,b_i\) 的代价,且由若干限制表示 \(i,j\) 划分至不同集合有 \(c_k\) 的代价。
考虑类似 P2057,将源点连每个点流量 \(a_i\) 表示划分到 \(B\) 集合,每个点连汇点 \(b_i\) 表示划分到 \(A\) 集合,有限制的点连双向边表示若有冲突必须割掉边,花费代价。
比较显然但不完全显然的集合划分模型。
首先不考虑“喜欢”关系,就是一个裸的集合划分。源点连 \(i\) 流量 \(d_i\) 保留表示愿意,连汇点 \(c_i\) 保留表示不愿意。
下面考虑刻画“喜欢”关系。发现难点在于确定每组是否合作。仅凭借选择是否愿意并不能推出是否合作,考虑对每组新建一个点表示是否合作。钦定若这个点连了源点,就表示合作,连汇点表示不合作。由于两个人中有一个人不愿意就必须不合作,所以若合作点与源点相连,且组内有人不愿意,就要不合法源汇连通,所以合作点向组内成员连流量 \(\infty\) 的边(表示强制限制,不能割断)。
然后处理关系就容易了。第一种情况 \(A\) 合作失败(合作点与汇点相连),且 \(B\) 选择愿意(与源点连),就 \(B\) 向 \(A\) 的组合作点连 \(a_i\) 的边,第二种情况 \(A\) 拒绝(连汇点),\(B\) 合作成功(合作点与源点连),就从 \(B\) 的合作点向 \(A\) 连 \(b_i\) 的边。
注意有的时候合作点和源点或汇点并没有任何间接相连的边,这时候代表决定其合作或不合作不需要代价,不会对答案产生影响(当时因为这个问题想了好久)。
感觉比上面那题好想一点。选文/理为两个集合,这题最小割模型其实是保留的边最大,所以边的流量和保留它表示的实际选的科目是一致的(下面默认源点为文,汇点为理)。
同样难以表示考虑新建节点,对于每个点周围全选文/理新建节点,只要有一个人选了相反的理/文,边就要割掉不能计入答案,容易想到全选文的点连原点,再连他周围的点和他自己,全选理是对称的。
和上面那题一样,不多说了。
需要注意的是表示都选 \(A\) 和都选 \(B\) 的决策点不能合并,不然会导致形如 \(s\to i \to \text{决策点} \to j \to t\) 的奇怪的流。见这个帖子。
以及注意边会达到 \(10^6\) 级别,数组不能开小。
最大权闭合子图
形如一个有向图,点有点权,选若干点,选了某个点必须选它的所有出点,要求选出的点权和最大(存在负点权,不然直接全选了)。
考虑集合划分模型,将点划分为“选”和“不选两个集合”。规定与源点相连表示选,与汇点相连表示不选,所以源点连 \(i\) 流量为 \(0\),\(i\) 连汇点流量为 \(w_i\)。
闭合子图的要求形如“选了 \(u\) 必须选 \(v\)”,由于要求最大割,所以 \(u\to v\) 连边 \(-\infty\),表示不能割的强制限制。
图连完之后要求最大割,但是最大割是 NP-hard 问题, 考虑权值取反求最小割。这时候出现了一些负权边,有负权的流是不好处理的,考虑对 \(w_i<0\) 的点 \(S\to i\) 和 \(i \to T\) 的边同时加上 \(w_i\) 的权值,然后再减去 \(\sum_{w_i>0} w_i\)(因为这两条边都割掉一定不优,都不割一定不行,所以一定会保留恰好一条边,也就是让答案增加 \(w_i\)),再取反。
其实上面的操作相当于先强制选了所有正权点,然后和源点连边,割掉表示不选,负权点和汇点连,割掉表示选,也就是 割=不选的正权点+选的负权点的绝对值=不选的正权点-选的负权点,那么 割-正权点和=-(选的正权点+选的负权点)=-答案,所以 答案=正权点的和-割。
很一眼吧。
也挺一眼的。
但是这题的区别是选择有先后顺序,也就是出现环就没有合法的选择方案,因此环和能到达环的点都不能选。拓扑排序预处理一下把这些点 ban 掉就行了。
较为复杂的最大权闭合子图,但并不难。
首先对于 \(d\) 数组,显然 \(d_{l,r}\) 选了 \(d_{l,r-1},d_{l+1,r}\) 必须要选。接下来考虑费用,对于 \(mx^2+cx\),分开计算。\(cx\) 和吃过的某种寿司个数有关,考虑直接将其计入 \(d_{i,i}\),对于 \(mx^2\) 只和是否吃过某种寿司有关,考虑对每种寿司建一个点,代价 \(mx^2\),选了 \(d_{i,i}\) 就必须选 \(a_i\) 对应的点。至此已转化为模板。
还是 CF 的题比较有趣 qwq
首先看到 \(k\) 个子集的并集大小 \(\geq k\) 这个东西一下就想到了 Hall 定理,考虑二分图匹配。左部点表示集合,右部点表示集合中的元素。
题目保证了任意集合的集合都有完美匹配,也就是如果对原图求一个完美匹配,只保留匹配的边,任意合法的答案的匹配一定存在。
因此如果选了一个左部点 \(u\),对于 \(u\to v\),若 \(v\) 不是 \(u\) 的匹配点,那么 \(v\) 的匹配点也必须被选上。可以发现所有合法的集合都可以这样生成。
然后就转化为最小权闭合子图,权值取反跑最大权闭合子图即可。
最小费用最大流
最小费用最大流 EK 板子:
bool spfa(){
memset(dis,0x3f,sizeof(int)*(n+1));
memset(inq,0,sizeof(int)*(n+1));
q.push(s),inq[s]=1;
flow[s]=inf,dis[s]=0; lst[t]=0;
while(q.size()){
int x=q.front(); q.pop(),inq[x]=0;
for(int i=hd[x];i;i=eg[i].nxt){
int y=eg[i].v;
if(!eg[i].f) continue;
if(dis[x]+eg[i].c<dis[y]){
dis[y]=dis[x]+eg[i].c;
lst[y]=i; flow[y]=min(flow[x],eg[i].f);
if(!inq[y]){
q.push(y); inq[y]=1;
}
}
}
}
return lst[t]!=0;
}
void EK(){
mxflow=0,mncst=0;
while(spfa()){
mxflow+=flow[t];
mncst+=flow[t]*dis[t];
int nw=t;
while(nw!=s){
eg[lst[nw]].f-=flow[t];
eg[lst[nw]^1].f+=flow[t];
nw=eg[lst[nw]^1].v;
}
}
}
费用流,限制在点上所以拆点。
由于取出数之后就变成 0,可以看成每个点能被经过无数次,第一次经过收益是 \(a_{i,j}\),之后都是 0。每个点拆成入点和出点,入点向出点连一条流量 1,费用 \(a_{i,j}\) 和一条流量 \(\infty\),费用 0 的边。可以向下和向右走,所以每个点的出点向它的右下两点的入点连 \((inf,0)\) 边。最后源点向入点,出点向汇点连边。
最多走 \(k\) 次,所以再建一个新汇点,原汇点向新汇点连流量为 \(k\) 的边。
轻度推式子可以发现每个工人修倒数第 \(i\) 辆车时贡献要 \(\times i\),考虑将每个工人修倒数 \(1\sim n\) 辆车各建一个点,每个车是一个点,然后边是好连的。
相比上面的区别主要是数据范围变大了,用上题的方式连边,边数 \(O(nmp)\) 太多了,只能获得 60pts。
发现其实有很多边是没有用的,真正用到的边其实只有 \(O(p)\) 条。任何时刻流只会经过每个厨师做的倒数 \(\leq x\) 道菜的点,费用流的贪心算法也保证了这一点。
考虑动态加点和边。称一个点的后继点为同一个厨师做倒数下一道菜的点,每次增广过后,加入增广路径经过的右部点的后继点。
需要注意使用 EK 必须每次增广完就加,不能增广完多条路径再加。因为可能先增广了不够优的路径而之后加入更优的路径,形成负圈。但是若使用 dinic 多路增广,每次增广的最短路长度相同,都是最优的,可以同时流。
下面连续几题都是这种最大权路径问题。
点有点权,考虑拆点为入点和出点,贡献放在入点和出点之间的边上。
对于不能同时经过的边,限制流量为 1 即可;对于只能经过每个点一次,限制入点出点间的边流量。
要求权值和最大,将权值取反跑最小费用最大流即可。
第三问是另外一个经典题,倒过来一遍 dp 就能解决。
一来一回的路径可以看成两条点不交的从源点至汇点的路径。
点只能经过一次,限制流量和上一题一样。
输出方案看哪些边有流量即可。注意不要在费用流过程中输出,之后可能会有反悔流。
这题限制每条边只能贡献一次,但可以多次经过。考虑拆边,一条边有权值,限流 1,另一条没权值,不限流。
这题稍微麻烦一些,首先是点权要拆点。每个点只取一次的限制可以参考上一题,给入点到出点的边限流。
注意对于探测车的数量的限制需要新建源点,而不是以 \((1,1)\) 作为源点(仅针对一些建图方法)。
对于第一问求最大流,使用 dinic(本来想偷懒和后面费用流一起用 EK,但是看了一下大概是会被卡的)。
对于第二问,扩容可以直接看成新建流量为 \(\infty\) 费用为 \(w\) 的边,然后就是一个费用流板子,使用 EK,当增加流量 \(>k\) 的时候直接结束,最多跑 \(k\) 次 spfa。
一眼看上去好像上下界费用流板子,实际上有更简单的做法。
注意到只是一些边强制流满,可以采用“骗流”的方式,用足够低的费用,使把这些边都流满一定更优。这个做法可以用于上下界费用流(拆边,一条边流量为 \(l\) 费用 \(-inf\),另一条流量为 \(r-l\) 费用 0)。
本题中,首先要拆点,点内的边要求流满 \(v_i\),因此设置流量 \(v_i\),费用 \(-inf\)(\(inf>ans\))。最后再把答案加上 $inf\sum v_i $。
稍微有点难的上下界费用流,通过骗流解决,拜谢第一篇题解。
首先这题看起来非常奇怪的地方是一个信使经过一个城市,可以让在这个城市的所有信使得到消息,类似“激活”的操作,这让构造流变得很困难。
考虑换一种眼光看待这个问题,首先我们要求每个城市都被信使经过,那对城市经过的第一个信使分类,一种是来自自己城市,花费 1,另一种是来自其它城市,在这里不产生费用。
对于限制经过,考虑拆点为入点出点,再拆边,拆为 \((1,-inf)\) 和 \((\infty,0)\) 两条,这样肯定先走第一条,注意到流了第一条边意味着这个城市得到了消息。
对于是否给信使初始计划,首先建每个城市信使点,然后 \(s\) 向它连 \((a_i,0)\) 边。然后若给了初始计划,一定是原本没得到消息,所以要连 \((1,1)\) 向入点然后经过 \((1,-inf)\) 边,反之这个城市已经得到消息,直接连向出点 \((\infty,0)\) 从这里出发即可。不难发现这样若这个点是某信使的起点,那一定先流 \((1,1),(1,-inf)\) 然后走 \((\infty,0)\),如果能得到消息就直接都流 \((\infty,0)\)。
原图不一定是 DAG,直接连边似乎会有负环,缩点后在 DAG 上建图即可,一个强连通分量的 \(a\) 即其中所有点的 \(\sum a\)。
无源汇上下界可行流
这是很多东西的的基础。
对于每条边考虑把下界流满,然后每条边的流量限制改为上界减下界。但是这个时候可能有一些点不满足流量平衡,此时新建超级源汇,将源点向流进大于流出的点连边,同理流进小于流出的向汇点连边来补齐流量。
此时流的大小为汇点连向源点的边流量。
有源汇上下界最大流
对于有源汇上下界可行流,流入源点和流出汇点的流量显然相等,所以汇点向源点连一条流量为 \(\infty\) 的边转化为无源汇上下界可行流。
而有源汇上下界最大流,就是先跑有源汇上下界可行流,记可行流为 \(flow1\),即为汇点连向源点的边的流量,然后断掉原图汇点连向源点的边和超源超汇连的所有边,从原图的源点再跑最大流 \(flow2\),最后的答案就是 \(flow1+flow2\)。
有源汇上下界最小流,则 \(flow2\) 为原图的汇点向源点的最大流,答案为 \(flow1-flow2\)。
做法正确性基于:对于任意一组可行流,在上面运行最大流算法总能获得最大流。原因是最大流算法是可撤销的。
有源汇上下界最大流模板。
有负圈的费用流
有负圈的费用流,不好跑 SPFA。
考虑把所有费用为负的边强制流满,费用变为原来的相反数。强制流满可以通过有源汇上下界网络流的方法解决,即汇点连源点并新建虚拟源汇。
网络流里面混一点二分图
最大匹配 = 最小点覆盖 (最大流 = 最小割)
最大独立集 = 点数 - 最小点覆盖
最大独立集 = 点数 - 最小点覆盖 秒了。
每个小行星相当于规定行和列至少选一个。最小点覆盖模型。
由 最小点覆盖 = 最大匹配,秒了。当然想写网络流也可以。
很久之前做过的题,大概是看到上面那题所以想起来了。和上面一题几乎一样,只是要输出答案。
注意到一个白点必须被涂上,并且只有两种方式,以其上方最近的黑点,或以其左方最近的黑点。
每个黑点有两种身份:向右涂或向下涂,对于每个黑点建两个点,向右涂在左边,向下在右边,形成二分图。对于白点,左边最近向上方最近连边表示两个至少选一个,于是就是一个最小点覆盖(最小割)问题,跑匈牙利或 Dinic 即可。
[2025-04-02] 现在才发现这题的输出方案并不显然,我自己想并不能想出来。
输出方案的方式:dfs 从左边的非匹配点出发,左边出发只走非匹配边,右边出发只走匹配边,走过的点标记,然后取左边没被标记的点和右边被标记的点。
首先右边不可能走到非匹配点,因为路径开头是左部非匹配点,所以这是一条增广路,存在更大匹配。然后左边没被标记的点也一定是匹配点,因为从非匹配点出发,所有非匹配点都被经过了。这说明了,我们只可能将匹配点选入最小点覆盖。
并且可以发现,一对匹配,要么两点都被标记,要么都不被标记,被标记时被右部点选,否则被左边选,也就是说每对匹配恰好有一个点选入最小点覆盖,可以发现这样的点覆盖 = 最大匹配。
不难证明这是最小的,因为再小就至少有一个匹配边没被覆盖。接下来证明这样可以覆盖全部的点:若存在一条边左右端点都没被这样覆盖到,那么它在上述 dfs 过程中左端点被 dfs 到但是右端点没有,这显然是不可能的。
于是证明完了。感觉并不简单。
题目是要求这个 DAG 对应的偏序集的最大反链。
首先这个 DAG 不一定是偏序集,要先对其求传递闭包使其满足传递性。
然后对于这个偏序集,建出其对应的二分图(\(u\to v\) 对应 左部的 \(v\to\) 右部的 \(u\)),然后反链对应不在最小点覆盖中的点,只需求出最小点覆盖(这个转化可以看看以前写的神秘笔记),用上面那题的方式求即可。
判断每个点能否在最大反链中,就强制选择然后删去有偏序关系的点,然后再求剩下的图最大反链是不是只比答案小 1。最大反链=最大独立集=点数-最小点覆盖=点数-最大匹配,但是要注意点数减少了。
还有一个细节就是求偏序集之后边数会变得很多,注意数组大小。

浙公网安备 33010602011771号