「刷题笔记」网络流

曾经感觉遥不可及的东西,今天终于学到了(有点感慨
联赛后生活变化挺大的,进入了真·奥赛班,回去后数语外物化生天天垫底……
竞赛课变成了一周10节开心,把文化课稳一下就可以快乐翘公自了awa(想得美

网络流的入门教程 (rvalue) 学长写的非常详细qaq(良心教程感动中国)所以这里主要总结一下解题过程
(快都去看学长博客

其实板子并不很难,重要的是建图的过程
注意:二分图左右两边的点是两个不同集合,如果发现最后连出来个对称的玩意是跑不了的

最大流

dinic板子
ll d[P],cur[P];
inline bool bfs(){
	//rint Q[P],fr=1,ta=0;
	queue<ll> Q;
	memset(d,0,sizeof d);
	d[s]=1; cur[s]=h[s]; Q.push(s);
	while(!Q.empty()){
		ll u=Q.front(); Q.pop();
		for(rint i=h[u];i;i=nx[i]){
			if(!d[to[i]]&&w[i]){
				d[to[i]]=d[u]+1;
				cur[to[i]]=h[to[i]];
				Q.push(to[i]);
				if(to[i]==t)return 1;
			}
		}
	}
	return 0;
}
inline ll dfs(ll u,ll fl){ 
	if(u==t)return fl;
	rint re=fl;
	for(rint i=cur[u];i&&re;i=nx[i]){
		if(d[to[i]]==d[u]+1&&w[i]){
			ll k=dfs(to[i],min(re,w[i]));
			if(!k)d[to[i]]=0;
			re-=k; w[i]-=k; w[i^1]+=k;
		}
		cur[u]=i;
	}
	return fl-re;
}

圆桌问题

首先人和桌子是一个典型的二分图模型,因为每个公司每桌只能坐一人,所以人点和桌子点之间的连边容量为1,根据题意可以看出源点和人点间连容量为人数的边,桌点和汇点间连容量为桌子大小的边,如果最大流等于总人数就说明有可行方案
关于输出方案:数据范围不大,直接平方枚举剩余流量看每条人点和桌点之间的边最后是否被占即可

蜥蜴

从源点向有蜥蜴的位置连边,可以一下跳出去的位置向汇点连边,可以互相到达的位置互相连边,控制点的经过次数常用套路是把每个点拆成入点和出点,控制他们之间连边的容量

星际战争

求一个满足条件的最小值,那么可以考虑二分,每次重新设定每个武器出去的总流量,看最大流是不是满足条件

士兵占领

求最小留下的士兵数,相当于求最多能删掉的士兵数,把每行每列最多能删掉的数量处理出来建图,能放的地方左右连边,二分+最大流即可

最小割

依靠最小割最大流定理,可以把一些问题利用最小割建出来图,再跑一遍最大流即可
常用于两个决策之间的抉择,如果不同的决策点之间有相互影响而不能确定单个决策是否在全局有利,可以把两种决策分成源点和汇点向决策点连代表决策价值的边,用最小割的性质求出较劣决策,他们的价值和也就是最小割,先求出所有价值和减去最小割就得到了决策价值最大值
建图的方式非常重要……

最大权闭合子图

给一个有向图,选择一些点,使任意一个点的所有后继都在选定的点集中,并使点权和最大化,点权有正有负
有些点的点权为负,这意味着在选择一些点时,如果它连接的点点权为负,就必须“支付一定代价”
那么在对一个点进行决策时,我们可以放弃收益也不支付代价,也可以获得收益并支付代价
对于点权为正的点,在需要选他时我们可以毫不犹豫地选他,因此我们需要考虑的是应当放弃哪些负权点
我们先把正权点点集 (S) 全部选出,可以得到与之对应的负权点集 (T) ,容易得到如果 (|S|<|T|) 那么应当在 (S) 中放弃一些点来使结果更优
建图,在原图所有边保留且容量为 (inf) 的基础上把源点向每个正权点连容量为权值的边,并把每个负权点向汇点连容量为权值相反数的边
通过对最大流最小割定理的理解可以得到在最小割中,割掉的是那些在最大流中能满流的边
而在我们建的图中,如果“源点-正权点”边满流了,就说明它连接负权边的代价不小于他本身的权值,这时选择这个点的“既得利益”是负的,所以需要删去这个点,这个点的收益应当删去
而对于“负权点-汇点”边,如果他们满流了,说明这个点的代价是小于对应正权点收益的,因此这条边的代价应该选上
诶???
那我们求一个最小割,不就直接相当于在原来选出所有正权点的基础上,放弃一些收益,得到一些代价的最优方案吗awa
因此可以这样建图awa好妙啊

二元关系最小割

例:人员雇佣
如果没有“一个选一个不选,会产生额外代价”的限制,那么相当与每个人的价值是他和别人配合价值的和,代价是雇佣他的代价,这样直接建一个和之前类似的图就行了
那么考虑怎么在原先得到的图的基础上处理这个新限制,在原图的意义上,如果保留 (i),删去 (j),即为删掉 ((i,t))((s,j)) 两条边,这个时候,我们想他多产生 (2*E_{i,j}) 的代价,即为连一条 ((i,j,2*E_{i,j})) 的边,那么为了形成割这条边一定会被割掉,从而产生代价

推广可以得到这样的问题:
从一堆东西里选出一些,对于每对点 (x=(i,j))

  • 两个同时选有 (a_x) 代价
  • 只选 (i)(b_x) 代价
  • 只选 (j)(c_x) 代价
  • 都不选有 (d_x) 代价

就可以得到更为普遍的建图方式,设源点为 (s) ,汇点为 (t) ,对于每对点 (x=(i,j))

[egin{cases}w[i][t]+w[j][t]&=a_x\ w[s][j]+w[i][j]+w[i][t]&=b_x\ w[s][i]+w[j][i]+w[j][t]&=c_x\ w[s][i]+w[s][j]&=d_xend{cases}]

通过这个可以解出所建图中每条边的容量,然后可以用最小割解决

最小割树

以上写到的都是需要构造一个给定源点汇点的图,然后跑最大流,有时我们需要求无向图中任意两点为源汇的最小割,这个时候就可以用到最小割树。

首先有一个结论:对于图 (G) 的一个以 (s), (t) 为源汇的割的两部分 (S), (T),如果有 (iin S), (jin T),那么 (minc(i,j)leq minc(s,t)) 。因为如果 (minc(i,j)ge minc(s,t)) ,那么 (s), (t) 的最小割中 ((i,j)) 是没有被切断的,矛盾。

考虑一次求割会把图分成两部分,最后每部分都成了一个点,也就相当于割 (n) 次,所以其实可以这样建出一棵树来,其中 ((i,j)) 边权值即为 (minc(i,j))

又有一个结论:在最小割树上,(minc(u,v)) 的值即为 (u ightarrow v) 上的最小值,证明比较长,这里就不抄一遍了

这样其实在把最小割树建出来之后,只需要倍增求一个路径最小值就行了,建树的过程相当于 (n)(dinic),所以是 (O(n^3m)) 的,然后 (nlog n)(dfs) 预处理,查询的时候就可以 (q log n)

平面图转对偶图

平面图:点和边之间有一种连法使得边与边之间没有交点的图
例题:[ICPC-Beijing 2006]狼抓兔子 && [NOI2010]海拔
最小割的图建出来以后,发现点数太多,(dinic) 跑不动
考虑割的过程:其实就是相当与一刀切掉一些边,把原图劈成两部分,那么可以理解成一条线经过原图中分成的几个区域把图分成两部分,由割的性质知这条线一定是连续的一条,那么把各个区域编号,连边,权值为割掉的代价,再在图外建虚拟源点/汇点,然后就可以用 (dijkstra) 跑最短路来求解了
dinic:???

最小割建模例

[CQOI2017]老C的方块

发现其实题里的限制就是蓝线两边的格子不能同时在另外三个方向有相邻的格子
对于网格图,常用的方法是染色:

考虑删的过程:

  • 一是删白点/黑点,那么连 ((s, ext{white},val))(( ext{black},t,val))
  • 二是删红点,如果删每组中的其中一个那么就不用管这一组中白点黑点的冲突,那么连 (( ext{red}_1, ext{red}_2,min(val_1,val_2)) 的边
  • 因为对于每组红点,不是情况一就是情况二,所以白点/黑点和对应的红点连 (( ext{white}, ext{red}_1,inf))(( ext{red}_2, ext{black},inf))

就建出来一个最小割模型,(inf) 边肯定不在最小割中,于是最小割就是最小代价

[NOI2009]植物大战僵尸

由题意可知存在“如果选xxx,就必须选xxx”的限制,于是可以用最大权闭合子图模型解决
注意如果需求关系形成了环,就不存在同时吃掉环上所有植物的方案,因此需要对原图建反图进行拓扑排序,来排除环的干扰

[六省联考2017]寿司餐厅

一定要记住最大权闭合子图的特征:选一个,就必须选另一个
由这题的费用不重复计算也能找到用这个方法的线索
转化一下题意:

  • 获得 (d_{i,j}),就一定获得 (d_{i,j-1})(d_{i+1,j})
  • 获得 (d_{i,i}),就一定获得 (a_i^2m)
  • 每获得一个 (d_{i,i}),就获得一个 (a_i)
    建图:
  • 对于第一条:直接连 (inf)
  • 对于第二条:所有 (d_{i,i}) 的点连 (inf) 到点 (a_i^2m) 上,相当于点 (a_i^2m) 有个点权
  • 对于第三条:相当于这个点有 (a_i) 点权
    那么接下来根据点权正负和题意向源汇建边即可

[TJOI2015]线性代数

首先需要推柿子……(更加感觉要好好学数学了……

[egin{aligned} D&=(A×B-C)×A^T\ &=sumlimits_{i=1}^n(sumlimits_{j=1}^nA_iA_jB_{i,j} -A_iC_i)\ &=sumlimits_{i=1}^nsumlimits_{j=1}^nA_iA_jB_{i,j} -sumlimits_{i=1}^nA_iC_i\ end{aligned} ]

不确定的是 (A),为了得到最大的 (D),我们考虑 (A)(D) 的贡献,发现:

  • (A_i=1),对 (D) 产生 (B_{i,i}-C_i)
  • 每对 (A_i=A_j=1)(D) 产生 (B_{i,j}+B_{j,i})

变形一下可以得到一个二元关系最小割的模型:

  • 同时选 (i,j)(B_{i,i}+B_{j,j}+B_{i,j}+B_{j,i}-C_i-C_j) 的贡献
  • 只选 (i)(B_{i,i}-C_i) 的贡献
  • 只选 (j)(B_{j,j}-C_j) 的贡献
  • 都不选没有贡献

那么可以用之前提到的方法解出所建图中每条边的权值:
(图炸了 咕了 见洛谷题解)

当然也有最大权闭合子图的方法,因为能看出式子中 (B_{i,j}) 这一项存在必须选 (i,j) 这两个会造成 (C_i,C_j) 贡献的点

费用流

给网络中的每条边一个费用 (c),若流量为 (f),则产生 (f*c) 的费用,现在需要求出在源到汇流量最大的前提下,总费用的最小值

费用流可以解决许多新问题,在求满足某条件的最小代价时,可以把“满足条件”转换成“能跑满最大流”,然后就可以用费用流求解了

spfa费用流

之前求最大流的过程就是不断地找增广路,现在要求费用最小,那我们就每次取 (sum c) 最小的那一条来增广即可,那么就变成了一个求最短路的过程,注意因为我们的图里有很多负权边(原因见后),所以求最短路时需要用到埋了半截的 (spfa),不过其实也有使用 (dijkstra) 的方法,就是用一些方式使边权为正,以后可能会学qwq

注意在建反边时,考虑增广的过程,如果增广的是一条反向边相当与把原来的边的费用抹掉,「所以反向边的费用应当取正向边费用的相反数」,于是这也是出现负权边的原因。

spfa费用流板子
ll dis[N],fl[N],la[N],pre[N],vis[N];
inline bool spfa(){
	memset(vis,0,sizeof vis);
	memset(dis,0x3f3f3f,sizeof dis);
	memset(fl,0x3f3f3f,sizeof fl);
	queue<ll> Q;
	dis[s]=0; vis[s]=1; pre[t]=-1; Q.push(s);
	while(!Q.empty()){
		ll u=Q.front(); Q.pop();
		vis[u]=0;
		for(int i=h[u];i;i=nx[i]){
			if(w[i].fi>0&&dis[u]+w[i].se<dis[to[i]]){
				dis[to[i]]=dis[u]+w[i].se;
				fl[to[i]]=min(fl[u],w[i].fi);
				pre[to[i]]=u;
				la[to[i]]=i;
				if(!vis[to[i]]){
					vis[to[i]]=1;
					Q.push(to[i]);
				}
			}
		}
	}
	if(pre[t]!=-1)return 1;
	else return 0;
}
ll mf=0,mc=0;
inline void mfmc(){
	while(spfa()){
		mf+=fl[t];
		mc+=fl[t]*dis[t];
		for(int i=t;i!=s;i=pre[i]){
			w[la[i]].fi-=fl[t];
			w[la[i]^1].fi+=fl[t];
		}
	}
}

费用流建模例

修车

考虑每个车被修时贡献的答案:如果他是这个师傅第倒数第 (i) 个修的,那么他就贡献了 (i*t) 的答案
那么可以把每个师傅拆成 (n) 个点,车向不同时间的师傅连边,最后需要满足每个车都被修就好了,那么可以费用流求解

[NOI2008]志愿者招募

很神仙的建图方式……
题中是按时间顺序的,可以源点向第一天连边,最后一天的下一天向汇点连边,这样每一天就能用一条只含一个端点的线段表示
每天有 (a_i) 个人要工作,这时他们要花钱,但我们不能确定每个工种的数量,只能确定一天在工作的人的总数量,而这个数量和 (c_i) 建立不了直接联系
那我们不妨就让 (c_i) 放在代表一个工种的边上,这条边从开始工作连到结束工作的下一天,那么这条边上就是这个工种的费用
而我们又要控制每天工作的人数,和每个工种的人数能建立直接联系的是这一天“没有在工作”的人数,他们加起来等于“是志愿者但不一定在工作”的总人数,这段没工作的人数就放在原来的那个类似于时间轴的东西上,我们不关心他到底一共有多少个人,所以直接设 (inf),那代表每一天的边就是 (inf-a_i)
这时再跑费用流,因为他保证是最大流,所以他一定会想方设法搞到 (inf) 个人,那就是会走一些带权边,而且保证是最小费用,就可以求出来了
(样例的建图)

无限之环

神仙题大概就是这种看题解一秒会,不看死活想不出来的吧……
首先可以想到一个点拆成四个方向,但不知道怎么处理旋转的情况,比较神仙的思路是在旋转后改变的相当于从变化的连接点间引一条边
因为问的是最少操作次数,所以我们给这条边赋边权,然后跑费用流,那么最小费用就是最小操作次数
然后,它难写,它真难写,为了提高读者的代码能力,不贴代码了
感受一下(调完这题后的理性发泄产物

上下界网络流

有时建出来的图每条边除了满足最大流量限制,还有一个流量下限,这个时候就需要上下界网络流
建议阅读达哥博客qwq

无源汇上下界可行流

给一个有向图,每条边有流量上下界,求是否能满足流量守恒
为了满足下界,我们不妨在开始时设所有边的流量都是他的下界,这个时候流量是不一定守恒的,但我们知道可行流中各边的流量肯定是大于他在这个流中的流量的,所以问题转化成在所有流量均取下界的图中每条边加上一定值,使他流量守恒
容易发现,如果初始流中对于每个点有 (g_i=in_i-out_i),那么在附加流中,有 (in_i'-out_i'=-g_i),即在添加附加流后,补成了一个流量守恒的图
现在的问题就是判断是否存在一个这样的附加流,在这个附加流中,对于原图中的边,他的上限即为原图中上限和下限的差,除此以外,为了满足每个点流入量与流出量之差的要求,我们建立虚拟源点/汇点,用来补齐这部分流量

  • 对于原图中 (g_i>0)(流入量大于流出量)的点,在附加流中要求他流出量大于流入量,需要从虚拟源点引过来这些多出来的流量
  • 对于原图中 (g_i<0)(流出量大于流入量)的点,在附加流中要求他流入量大于流出量,需要把这些多出来的流量从虚拟汇点引走

对这个图用 (dinic) 跑最大流,则当所有与源点/汇点连的边都满流时,每个点流入流出量之差满足要求
这种情况等价于源点连边上限之和等于最大流,原因如下:

原图中每条边对两个端点的贡献一正一负能抵消掉,
也就是说 (sum g_i=0)
也就是说 (g_i) 中负值的绝对值之和=正值的绝对值之和
也就是源点连边上限之和=汇点连边上限之和,
若源点连边上限之和等于最大流,
则汇点连边上限之和等于最大流,
则汇点所有连边在最大流中都满流

那么只需要判断最大流是否为源点连边上限之和即可,如果存在可行流,那么此时把附加流中边流量加上他在原图中的下限就能得到可行流中每条边的流量

例题:ZOJ2314 Reactor Cooling
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 305
#define E 100005
#define inf 100000000000

ll T,n,m,s,t,t1,t2,t3;

ll to[E],nx[E],h[N],w[E],id[E],tot=1;
inline void add(ll a,ll b,ll c,ll i){ to[++tot]=b; nx[tot]=h[a]; h[a]=tot; w[tot]=c; id[tot]=i; }
inline void link(ll a,ll b,ll c,ll i){ add(a,b,c,i); add(b,a,0,i); }

ll d[N],cur[N];
inline bool bfs(){
	queue<ll> Q; memset(d,0,sizeof d);
	d[s]=1; cur[s]=h[s]; Q.push(s);
	while(!Q.empty()){ ll u=Q.front(); Q.pop();
		for(int i=h[u];i;i=nx[i]){
			if(!d[to[i]]&&w[i]){
				d[to[i]]=d[u]+1; cur[to[i]]=h[to[i]]; Q.push(to[i]);
				if(to[i]==t)return 1;
			}
		}
	}
	return 0;
}
inline ll dfs(ll u,ll fl){
	if(u==t)return fl;
	ll re=fl;
	for(int i=cur[u];i&&re;i=nx[i]){
		if(d[to[i]]==d[u]+1&&w[i]){
			ll k=dfs(to[i],min(re,w[i]));
			if(!k)d[to[i]]=0;
			w[i]-=k; w[i^1]+=k; re-=k;
		}
		cur[u]=i;
	}
	return fl-re;
}
inline ll dinic(){
	ll mx=0,tmp;
	while(bfs()){ while(tmp=dfs(s,inf))mx+=tmp; } 
	return mx;
}

ll g[N],mi[E],ans[E];
inline void clear(){ memset(h,0,sizeof h); tot=1; memset(g,0,sizeof g); }

inline ll read(){
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

int main(){
	T=read();
	while(T--){
		n=read(); m=read(); s=n+1; t=n+2; ll sum=0; clear(); 
		for(int i=1;i<=m;i++){
			t1=read(); t2=read(); mi[i]=read(); t3=read();
			g[t1]-=mi[i]; g[t2]+=mi[i];
			link(t1,t2,t3-mi[i],i);
		}
		for(int i=1;i<=n;i++){
			if(g[i]<0)link(i,t,-g[i],0);
			else link(s,i,g[i],0),sum+=g[i];
		}
		if(dinic()==sum){
			puts("YES");
			for(int i=2;i<=tot;i++){
				if(id[i]&&i%2)ans[id[i]]=mi[i/2]+w[i];
			}
			for(int i=1;i<=m;i++)printf("%lld
",ans[i]);
		}
		else puts("NO");
		if(T)puts("");
	}
	return 0;
}

有源汇上下界可行流

发现源点汇点并不流量守恒,但是源点流出量等于汇点流入量,那么可以从汇点向源点连一条 ([0,inf]) 边然后按上面的方法求这个图的无源汇上下界可行流,最后把源点汇点之间的边删去即可,其实此时原图流量大小就是这条边的反边的流量

有源汇上下界最大流

首先求一个有源汇上下界可行流,通过 ((t,s)) 的反向边可得当前流
删去原图以外的边后,在原图的残量网络上跑 ( ext{dinic}(s,t)),再加上之前可行流的大小即可
一定要注意,所有有上下界的边都应当对 (g) 数组有更新,更新的时候注意编号,不要少写个 (+n) 啥的

洛谷 P5192 Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 2005
#define E 200005
#define inf 10000000000

ll n,m,s0,t0,ss,tt,t1,t2,t3,t4,t5;
ll g[N];

ll to[E],nx[E],h[N],w[E],tot=1;
inline void add(ll a,ll b,ll c){ to[++tot]=b; nx[tot]=h[a]; h[a]=tot; w[tot]=c; }
inline void link(ll a,ll b,ll c){ add(a,b,c); add(b,a,0); }
inline void clear(){ memset(h,0,sizeof h); tot=1; memset(g,0,sizeof g); }

ll d[N],cur[N];
inline bool bfs(ll s,ll t){
	queue<ll> Q; Q.push(s); memset(d,0,sizeof d); d[s]=1; cur[s]=h[s];
	while(!Q.empty()){ ll u=Q.front(); Q.pop();
		for(int i=h[u];i;i=nx[i])if(!d[to[i]]&&w[i]){
			d[to[i]]=d[u]+1; cur[to[i]]=h[to[i]]; Q.push(to[i]); if(to[i]==t)return 1;
		}
	}
	return 0;
}
inline ll dfs(ll u,ll fl,ll t){
	if(u==t)return fl; ll re=fl;
	for(int i=cur[u];i&&re;cur[u]=i,i=nx[i])if(d[to[i]]==d[u]+1&&w[i]){
		ll k=dfs(to[i],min(re,w[i]),t); if(!k)d[to[i]]=0;
		re-=k; w[i]-=k; w[i^1]+=k;
	}
	return fl-re;
}
inline ll dinic(ll s,ll t){ ll mx=0,tmp;
	while(bfs(s,t)){ while(tmp=dfs(s,inf,t))mx+=tmp; } return mx;
}

inline ll read(){
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

int main(){
	while(~scanf("%lld%lld",&n,&m)){ 
		s0=n+m+1; t0=n+m+2; ss=n+m+3; tt=n+m+4; clear();
		for(int i=1;i<=m;i++){
			t1=read(); link(i+n,t0,inf-t1);
			g[i+n]-=t1; g[t0]+=t1;
		}
		for(int i=1;i<=n;i++){
			t1=read(); t2=read(); link(s0,i,t2);
			for(int j=1;j<=t1;j++){
				t3=read()+1; t4=read(); t5=read();
				link(i,t3+n,t5-t4);
				g[i]-=t4; g[t3+n]+=t4;
			}
		}
		ll sum=0;
		for(int i=1;i<=n+m+2;i++){
			if(g[i]>0)link(ss,i,g[i]),sum+=g[i];
			if(g[i]<0)link(i,tt,-g[i]);
		}
		link(t0,s0,inf); 
		if(dinic(ss,tt)==sum){ ll tf=w[tot];
			for(int i=h[ss];i;i=nx[i])w[i]=w[i^1]=0;
			for(int i=h[tt];i;i=nx[i])w[i]=w[i^1]=0;
			w[tot]=w[tot-1]=0;
			printf("%lld
",tf+dinic(s0,t0));
		}
		else puts("-1"); puts("");
	}
	return 0;
}

有源汇上下界最小流

首先用之前的方法还是可以得到一个可行流,在这个基础上求最小流相当于在可行流上退流,
其本质就是找 ((t,s)) 由反边构成的路径上的增广路,那么可以用求 ( ext{dinic}(t,s)) 解决,这时的最大流就是可行流上最大能退掉的流,所以直接拿可行流流量减去即可
这样的退流并不会使一些边流量低于下限,因为我们已经规定好初始流为流量下限。把原图以外的边去掉以后,这个图其实就是之前要求的附加流,我们在附加流上反跑最大流来退流,可以保证最终得到的网络符合要求,且流量是最小的

例题:洛谷 P4843 清理雪道
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 305
#define E 30005
#define inf 100000000000

ll n,t1,t2,s0,t0,ss,tt;
ll g[N],sum=0,tr,tk;

ll to[E],nx[E],w[E],h[N],tot=1;
inline void add(ll a,ll b,ll c){ to[++tot]=b,nx[tot]=h[a],h[a]=tot,w[tot]=c; }
inline void link(ll a,ll b,ll c){ add(a,b,c); add(b,a,0); }

ll d[N],cur[N];
inline bool bfs(ll s,ll t){
	queue<ll> Q; Q.push(s); memset(d,0,sizeof d); d[s]=1; cur[s]=h[s];
	while(!Q.empty()){ ll u=Q.front(); Q.pop();
		for(int i=h[u];i;i=nx[i])if(!d[to[i]]&&w[i]){
			d[to[i]]=d[u]+1; cur[to[i]]=h[to[i]]; Q.push(to[i]); if(to[i]==t)return 1;
		}
	}
	return 0;
}
inline ll dfs(ll u,ll fl,ll t){
	if(u==t)return fl; ll re=fl;
	for(int i=cur[u];i&&re;cur[u]=i,i=nx[i])if(d[to[i]]==d[u]+1&&w[i]){
		ll k=dfs(to[i],min(re,w[i]),t); if(!k)d[to[i]]=0;
		re-=k; w[i]-=k; w[i^1]+=k;
	}
	return fl-re;
}
inline ll dinic(ll s,ll t){ ll mx=0,tmp;
	while(bfs(s,t))while(tmp=dfs(s,inf,t))mx+=tmp; return mx;
}

inline ll read(){
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

int main(){
	n=read(); s0=n+1; t0=n+2; ss=n+3; tt=n+4;
	for(int i=1;i<=n;i++)link(s0,i,inf),link(i,t0,inf);
	for(int i=1;i<=n;i++){ t1=read();
		for(int j=1;j<=t1;j++){ t2=read();
			link(i,t2,inf-1);
			g[i]--; g[t2]++;
		}
	}
	for(int i=1;i<=n+2;i++){
		if(g[i]>0)link(ss,i,g[i]),sum+=g[i];
		if(g[i]<0)link(i,tt,-g[i]);
	}
	link(t0,s0,inf);
	tr=dinic(ss,tt); //cout<<tr<<' '<<sum<<endl; 
	tr=w[tot];
	for(int i=h[ss];i;i=nx[i])w[i]=w[i^1]=0;
	for(int i=h[tt];i;i=nx[i])w[i]=w[i^1]=0;
	w[tot]=w[tot-1]=0;
	tk=dinic(t0,s0);
	//cout<<tr<<' '<<tk<<endl;
	printf("%lld",tr-tk);
	return 0;
}

无源汇上下界最小费用可行流

感觉直接写有源汇的有点突兀所以在这里插一节 名字怎么越来越长了
用之前的类似思路,先考虑满足下界,然后求出附加流,那么答案即为下界这部分加上附加流的部分
而附加流这部分在我们用虚拟源点/汇点建出图后,求可行流的思路是跑一个最大流,而这里要求最小费用可行流,所以就跑最小费用最大流即可

有源汇上下界最小费用可行流

按照套路(大概可以叫套路吧)连接 ((t,s)),转成无源汇,就可以按上面的方法求解啦

洛谷 P4043 [AHOI2014/JSOI2014]支线剧情
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 305
#define E 15005
#define inf 1000000000000

ll n,s0,t0,ss,tt,t1,t2,t3,tk=0;
ll g[N];

ll to[E],nx[E],h[N],w[E],c[E],tot=1;
inline void add(ll a,ll b,ll _w,ll _c){ to[++tot]=b; nx[tot]=h[a]; h[a]=tot; w[tot]=_w; c[tot]=_c; }
inline void link(ll a,ll b,ll _w,ll _c){ add(a,b,_w,_c); add(b,a,0,-_c); /*cout<<"LINK: "<<a<<' '<<b<<' '<<_w<<' '<<_c<<endl;*/ }

ll dis[N],le[N],lp[N],fl[N]; bool vis[N];
inline bool spfa(ll s,ll t){
	memset(dis,127,sizeof dis); memset(vis,0,sizeof vis); memset(fl,127,sizeof fl);
	dis[s]=0; queue<ll>Q; Q.push(s); vis[s]=1; lp[t]=-1;
	while(!Q.empty()){ ll u=Q.front(); Q.pop(); vis[u]=0; //cout<<u<<endl;
		for(int i=h[u];i;i=nx[i]){ ll v=to[i]; //cout<<u<<' '<<v<<' '<<w[i]<<endl;
			if(dis[v]>dis[u]+c[i]&&w[i]>0){
				dis[v]=dis[u]+c[i];
				lp[v]=u; le[v]=i; fl[v]=min(fl[u],w[i]);
				if(!vis[v]){ vis[v]=1; Q.push(v); }
			}
		}
	}
	if(lp[t]==-1)return 0;
	else return 1;
}
ll mf,mc;
inline void mfmc(ll s,ll t){ mf=mc=0;
	while(spfa(s,t)){ //cout<<1<<endl;
		mf+=fl[t]; mc+=dis[t]*fl[t];
		for(int i=t;i;i=lp[i])w[le[i]]-=fl[t],w[le[i]^1]+=fl[t];
	}
}

inline ll read(){
	ll f=0,s=0; char c=getchar();
	while(c>'9'||c<'0')f=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')s=(s<<3)+(s<<1)+(c^'0'),c=getchar();
	return f?-s:s;
}

int main(){
	n=read(); s0=n+1; t0=n+2; ss=n+3; tt=n+4;
	link(s0,1,inf,0); //g[s0]--; g[1]++;
	for(int i=1;i<=n;i++){
		t1=read(); link(i,t0,inf,0);
		for(int j=1;j<=t1;j++){
			t2=read(); t3=read(); tk+=t3;
			link(i,t2,inf-1,t3);
			g[i]--; g[t2]++;
		}
	}
	for(int i=1;i<=n+2;i++){ //cout<<i<<' '<<g[i]<<endl;
		if(g[i]>0)link(ss,i,g[i],0);
		if(g[i]<0)link(i,tt,-g[i],0);
	}
	link(t0,s0,inf,0);
	mfmc(ss,tt); //cout<<mf<<' '<<mc<<' '<<tk<<endl;
	printf("%lld",mc+tk);
	return 0;
}

上下界建模例

bzoj2406 矩阵

题目的人话翻译:给一个 (n*m) 的矩阵 (A),对于一个元素在 ([L,R]) 中取的 (n*m) 的矩阵 (B),有矩阵 (C) 使 (C_{i,j}=|A_{i,j}-B_{i,j}|) 求这个矩阵 (C) 每行每列之和中最大值的最小值

矩阵的常用套路是把行和列弄一个二分图,左右之间连边来表示每一个坐标,在这个题中,不关心 (B) 具体是什么,所以可以二分答案,考虑如果一个答案可行,即存在方案,使:

[forall i,|sumr_{A_i}-sumr_{B_i}|<=mid ]

[forall i,|sumc_{A_i}-sumc_{B_i}|<=mid ]

即:

[forall i,sumr_{B_i}in [sumr_{A_i}-mid,sumr_{A_i}+mid] ]

[forall i,sumc_{B_i}in [sumc_{A_i}-mid,sumc_{A_i}+mid] ]

这个范围限制就是一个上下界模型,之前 ([L,R]) 的限制也一样,那么只需要判断是否存在有源汇上下界可行流即可

原文地址:https://www.cnblogs.com/zzzuozhe-gjy/p/14139800.html