网络瘤24题解+总结

updata:删除了板子题部分,因为除了使得本文冗余之外没有任何作用。

顺序主观决定,

至于为什么不是24个,嘿嘿,机器人那个被我吃了

至于为什么不是23个,嘿嘿,软件的补丁是好东西,可不是毒瘤

众所周知,在网络流24里,有两个假题。。。。一个是IDA*的搜索,一个是状态压缩的最短路。。。

其实,硬要说,是三个假题,因为某题严格来说是最短路。。。

如果不行的话,其实,有五个题正解和网络流没有关系。。

网络流 24 题-思维提高篇

(注:水题在下面的基础技巧篇)

太空飞行计划

教训:(开始想费用流,搞半天出不来)

网络流解决最大/小费用问题,要么最小割最大流,要么最小费用流

最小费用流的前提是最大流,所以在有一些点可以不取换最优代价的时候,是不可行的。

当每个点都只能取一次的时候,可以考虑费用化为容量,求最小割/最大流

回到这个题,假如我们把一个实验所需器材用有向边连起来,问题就化为了:

在一个二分图中,左部点向右部点有若干连边,若选择了某一个左部点,则所连右部点必选。求最后最大的点权和

将其转化为有向图,问题化为:

在一张有向图中,选出一个子图,满足选择了一个点,它的后继所有点都必选,求最大和

最大权闭合子图模型

这个模型是用来解决上述问题的。

定理:若将源点与所有正权点连边,容量为点权,汇点与所有负权点连边,容量为点权的相反数,同时保留原有向图所有边,容量正无穷,则正权点的点权和减去最小割即为所求。

证明

被割掉的边的意义:

  1. (s,u,w[u]) 被割,表示不选 u
  2. (v,t,w[v]) 被割,表示 v

这样就可以求出最大权闭合子图的点集,进而求出图 G

引理:Dinic 算法最后一次分层(failed)的 dep 数组就可以用来判断可达性。可达的点构成了最大权闭合子图

由此,我们可以由源点向每个实验连边,容量为收益,由汇点向每个器材连边,容量为代价。由每个实验向所需器材连边,容量正无穷。

若某实验没被割,则加入答案。若某器材被割,则加入答案。

    read(m);read(n);int ans=0;s=n+m+1,t=n+m+2;num=t; 
	for(int i=1;i<=m;i++){
		int y;
		int x=read(y);
		add(s,i,y);ans+=y;
		while(!x){
			int y;x=read(y);
			add(i,m+y,inf);
		}
	}//鬼畜输入
	for(int i=1;i<=n;i++){
		int x;read(x);add(m+i,t,x); 
	}
	ans-=dinic();
	for(int i=1;i<=m;i++)if(dep[i])cout<<i<<" ";
	cout<<"\n";
	for(int i=m+1;i<=n+m;i++)if(dep[i])cout<<i-m<<" ";cout<<"\n";
	cout<<ans<<"\n";

最小路径覆盖问题

给定一个DAG,求其最小路径覆盖。

这里体现的重要思想有两个:

  1. 将一个点的不同状态拆分,化为左右部,分别求解
  2. 部分计算,合并答案
  3. 正难则反,拆分——>合并

首先,我们将在DAG里路径的过程过来,变路径。最初将 n 个点都单独是做一条路径。

然后我们考虑合并路径的操作。要路径条数最少,则合并次数最多。

注意到有向图中,每个点有两个状态:作为该边的入点,作为该边的出点。而在路径合并中,显然要区分开这两种状态,只能入状态向出状态合并。

为什么?我们需要的是合并次数,而非正常建图的流量。求最大流,也即最大化流量,而现在我们要最大化合并次数,所以合并次数就是我们的流量,而合并次数为了判重(一个点最多给1次),必须一对对拆。

显然原图一条边最多提供1次合并,那么对于原图的每一条边,由入状态向出状态的另一端点连边,容量1,表示提供1的合并次数。

那么对于入状态的点,可以找一个点合并,则由源点向其连边,容量1

对于出状态的点,可以被一个点合并,则由其向汇点连边,容量1

综上,我们将一个点拆为 i,i+n,建立边 (s,i,1),(i+n,t,1),(u,v+n,1)跑最大流,即可得到最大合并次数。

用总点数减去最大合并次数即可得到最小路径数。

关于方案输出?网络流的方案输出一般体现在满流边和残量网络上,当然也可以像动态规划一样记录上一个点

这里常用的方法是由汇点开始找残量网络,找到的路径就对应了合并关系。

但更简单的方法?注意到原图里边的容量全是1,那么就必有 lst 唯一,在网络流DFS过程中直接记录 lst,倒回来输出即可。

请注意,这里还没完,我们只得到了若干对合并关系,但不能就此输出整个路径。我们可以通过合并关系确定每个点在合并后路径上的 lst,然后根据路径终点直接跳就好(终点判断:标记是否被记录过作为合并的主动方)

vector<int>f[N];int dcc; 
int vis[N],suf[N];
void init(){
	cin>>n>>m;s=n+n+1,t=n+n+2;num=t;
	for(int i=1;i<=m;i++){
		int u,v;cin>>u>>v;add(u,v+n,1);
	}
	for(int i=1;i<=n;i++)add(s,i,1);
	for(int i=n+1;i<=n<<1;i++)add(i,t,1);
	int ans=dinic();
	for(int i=head[t];i;i=nxt[i]){
		if(cost[i]==1){
			++dcc;int x=ver[i];
			while(x!=s){
				f[dcc].push_back(x),x=lst[x];
			}
			if(f[dcc][1]>n)f[dcc][1]-=n;
			if(f[dcc][0]>n)f[dcc][0]-=n;
			suf[f[dcc][1]]=f[dcc][0];vis[f[dcc][0]]=1;
		}
	} 
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			int x=i;
			while(x){
				cout<<x<<" ";x=suf[x];
			}cout<<"\n";
		}
	}
	cout<<n-ans<<"\n";
}

下面来看一个同为网络流24题的变形题。

魔术球问题

一个显然的结论是:柱子越多,可以投入的数越大,我们可以考虑逐步增大答案。

注意到我们如果将两个和为完全平方数的数连边,限制大连小,那么一个石柱其实就对应了这样一张DAG的简单路径。问题化为求该DAG的最小路径覆盖。当这个路径覆盖数超过 k 说明上一个枚举的数即为答案,此时重新建图,跑一边最小路径覆盖,合并路径后直接输出即可。注意输出顺序。

vector<int>f[N];int dcc,ans; 
void dinic(){
	while(bfs()){
		for(int i=1;i<=num;i++)cur[i]=head[i];
		ans+=dfs(s,0x3f3f3f3f);
	}
//	cout<<ans<<"\n";
}
int vis[N],suf[N];
void init(){
	cin>>n;
	s=1,t=2;int d=0;
	for(int i=1;;i++){
		int x=i*2+1,y=(i+1)*2;
		add(s,x,1);add(y,t,1);
		for(int j=1;j<i;j++){
			int q=sqrt(i+j);if(i+j==q*q){
				add(x,(j+1)*2,1);
			}
		}
		num=y;
		dinic();
		if(i-ans==n+1){
			cout<<i-1<<"\n";d=i-1;break;
		}
	}
	for(int i=1;i<=num;i++)head[i]=0;tot=1;
	for(int i=1;i<=d;i++){
		int x=i*2+1,y=(i+1)*2;
		add(s,x,1);add(y,t,1);
		for(int j=1;j<i;j++){
			int q=sqrt(i+j);if(i+j==q*q){
				add(x,(j+1)*2,1);
			}
		}
	}
	num=d*2+2;
	dinic();
	for(int i=head[t];i;i=nxt[i]){
		if(cost[i]==1){
			++dcc;int x=ver[i];
			while(x!=s){
				f[dcc].push_back(x),x=lst[x];
			}
			f[dcc][0]=f[dcc][0]/2-1;
			f[dcc][1]/=2;
			suf[f[dcc][0]]=f[dcc][1];vis[f[dcc][1]]=1;
		}
	} 
	for(int i=1;i<=d;i++){
		if(!vis[i]){
			int x=i;
			while(x){
				cout<<x<<" ";vis[x]=1;x=suf[x];
			}cout<<"\n";
		}
	}
}

最长 k 可重区间集问题

题意:给定若干 开区间,求选出一个区间集 S,使得其选出的区间长度和最大且不存在一个点被覆盖了 k 次以上。

真的是非常巧妙的问题!

首先,虽然这是个区间操作问题,我们仍然可以将其抽象为(亦或者说微观化?)序列元素操作问题。我们将区间端点离散化后,这题就变成了:选出若干区间,使得每个点覆盖次数小于等于 k,这里的覆盖变成了严格的 l<x<r。开区间嘛。

我们需要解决的问题有两个:

  1. 如何表示区间覆盖
  2. 如何表示一个点的覆盖次数

注意到我们要求的是最大长度为区间长度和,而非覆盖范围。每一个点必定在最后都是合法的,而我们解决问题的基本元素就是点,则最终网络满流,所以这个长度和必定是一个费用,本题为最大费用最大流问题。

那么用费用表示区间长度,则只能用流量表示覆盖次数了。如果我们设经过一个点的流量表示这个点的覆盖次数,可以吗?发现难以维护区间被覆盖的问题,本质原因是在流网络中,加边的后果就是导致分流。分流有两种情况,一是从源点引流,增加该边以后的流量,而是从其他点分流,使得经过其他点的流量减少,而我们影响的对象是该区间内的点,只能选择分流该区间的流量。此时就是本题做法的最精妙之处了:设经过一个点的流量表示其还能被覆盖几次。正难则反的思想是处处可见呢!

有了这个状态定义,整个问题就变得容易了起来。状态的定义是解决问题的大前提,类似于战略

我们为了限制一个点的被覆盖次数最多 k 次,显然必须要连边 (i,i+1,k,0),其中分别表示出点,入点,容量,费用。这限制了通过一个点的流量最多为 k

那么我们考虑使得通过一个点的流量减少1,则有一条容量为1的,费用更优的边在旁边分流。故建立 (l,r,1,len)

有最大流的限制,使得求该网络的最大费用即为所求。

回顾整个题,不难发现闪烁思想光芒的点主要有三个:

  1. 问题转化——将区间选择更换角度为点的选择
  2. 状态定义——运用推理的方法,找到最优的状态
  3. 最大流性质—利用所有点必选的最大流限制,来限制区间选择

在这里提一句,在费用流问题中,流量是第一关键字,费用只是第二关键字。一般我们用费用流求最优化问题时,都有所有状态必选的性质。

void init(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>l[i]>>r[i];if(l[i]>r[i])swap(l[i],r[i]);a[++m]=l[i],a[++m]=r[i];
	}
	a[++m]=0;
	sort(a+1,a+m+1);
	m=unique(a+1,a+m+1)-a-1;
	for(int i=1;i<=n;i++){
		int L=lower_bound(a+1,a+m+1,l[i])-a;
		int R=lower_bound(a+1,a+m+1,r[i])-a;
		add(L,R,1,l[i]-r[i]); 
	}
	for(int i=1;i<=m;i++)add(i,i+1,k,0);s=m+2,t=m+3;num=t;
	add(s,1,k,0);add(m+1,t,k,0);
	zkw(-1);
}

最长 k 可重线段集问题

类比上题,我们现在来解决线段集问题。显然,在这个题里面,y 除了计算长度之外没有任何用处。我们只看 x。事实上令 li=xi,1,ri=xi,2,则就化为了上一个问题。

真的吗?不是的。考虑存在两条线段:li=ri=x,则建图后,这两线段不交,但应该交(没有公共边,注意开区间是用边表示点的),这怎么办?

处理这种问题,在数轴上,我们选择扩域,将每个点扩两个域,中间部分用来表示该点。所以连接 (2li,2ri) 即可,对于上述这种情况,连边 (2x,2x+1) 就OK。

好的交上去91pts,为什么?

注意到线段 (p,q),(p,p),若扩域后连边为 (2p,2p+1),(2p,2q),则两区间有交了。但本来应该不交的。所以我们连 (2p+1,2q)

综上,对于 li=ri,连边 (2li,2ri+1)。否则连边 (2li+1,2ri)。然后离散化连边,相邻点容量 k费用0

扩域时要格外注意相等的情况怎么处理

void init(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		int x,y;
		cin>>l[i]>>x>>r[i]>>y;if(l[i]>r[i])swap(l[i],r[i]);
		len[i]=sqrt((l[i]-r[i])*(l[i]-r[i])+(x-y)*(x-y));
		l[i]<<=1;r[i]<<=1;
		if(l[i]==r[i])r[i]++;
		else l[i]++;
		a[++m]=l[i],a[++m]=r[i];
	}
	a[++m]=0;
	sort(a+1,a+m+1);
	m=unique(a+1,a+m+1)-a-1;
	for(int i=1;i<=n;i++){
		int L=lower_bound(a+1,a+m+1,l[i])-a;
		int R=lower_bound(a+1,a+m+1,r[i])-a;
		add(L,R,1,-len[i]); 
	}
	for(int i=1;i<=m;i++)add(i,i+1,k,0);s=m+2,t=m+3;num=t;
	add(s,1,k,0);add(m+1,t,k,0);
	zkw(-1);
}

星际转移问题

结合时间轴的网络流问题。

先判无解再说。如何?将一个飞船经过的所有空间站全部合并在一起,表示可以互达。这一步用并查集。如果最后地球月球不在同一连通块,就没了。

然后我们考虑求解这个问题。注意问题与时间有关,而数据很小,刻画空间站状态需要用到时间,所以我们考虑对每一时间的空间站都开一个状态,但需要当前时刻向下一时刻的空间站连边,容量无穷,表示时间的转移,人留在了这个太空站。接着,源汇点分别向该时刻的地球月球连边,容量无穷。而每一个飞船可以计算得到上一时刻的某个空间站可以转移到当前时刻的空间站,故连边,容量为该飞船容量。

下面我们再来看一个与时间有关的网络流题。

餐巾计划问题

本题的核心思想是:将同一事物的不同状态拆点

首先注意到餐巾分为新旧两类,且第 i 天有 ri 新餐巾转化为旧餐巾。对于状态之间的不同转化,上题也有所体现,那便是通过源汇点,进行一定的费用限制,使得新化旧。毕竟除了源汇点,其他点必须流量守恒。

那么我们用i表示新餐巾,i+n 表示旧餐巾。建立 (s,i+n,ri,0)以及(i,t,ri,0)转化新旧两个状态,等价于给出旧的,收回新的借助其他点转化,而非直接转化

再者,对于题中的几个操作,显然可以连边 (s,i,+,p),(i+n,i+d1,+,c1),(i+n,i+d2,+,c2),其中 d1,d2,c1,c2 分别表示两种洗餐巾的时间间隔和费用。

同时,当天没用的旧餐巾可以转递到下一天,有 (i+n,i+n+1,+,0)。这里为什么不转移新餐巾呢?因为可以自己当天买。没必要提前买。

本题有三分结合贪心的算法,可以做到 O(nlogn)

void init(){
	cin>>n>>g>>d1>>c1>>d2>>c2;
	s=n+n+1,t=n+n+2,num=t;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)add(s,i+n,a[i],0),add(s,i,inf,g),add(i,t,a[i],0); 
	for(int i=1;i<n;i++)add(i+n,i+n+1,inf,0);
	for(int i=1;i<=n;i++){
		if(i+d1<=n)add(i+n,i+d1,inf,c1);
		if(i+d2<=n)add(i+n,i+d2,inf,c2);
	}
}

最长不下降子序列问题

本题核心:动态规划结合网络流话说怎么有点类似某教辅品牌所谓“三阶分层练”

首先,最长不下降子序列可以使用动态规划求出,解决第一问。

我们现在来思考动态规划给我们的 f 数组有何性质。

引理:原序列中,任意一个最长不下降子序列的第 iax,都有 fx=i
证明:如果这样的 x 在另一个最长不下降子序列中处第 j(j>i),则显然存在子序列 b ,前 j 项与序列2一致,而后面接上第一个序列,我们就能得到一个长度大于二者的最长不下降子序列,与两个序列的定义矛盾,引理成立。

那这就简单了,说明此为一个 分层图网络流f 就是其处于的层数。因为每个点只能取一次,拆点,连容量1的边,将满足 i<j,aiaj,fi=fj1i,j 连边 (i+n,j,1),然后将第一层的所有点与源点连边,最后一层的所有点与汇点连边,跑最大流即可求解。本质上这条路径便是一个最长不下降子序列。

第三问?将源点与 1 连的边、1 出入点间的边容量改为 +,将 n出入点间的边也改为 + 即可。若 fn 是最大值,说明其可以作为终点,则把它的出点与汇点的边改为 +

signed main(){
	ios::sync_with_stdio(false);
	cin>>n;s=n+n+1,t=n+n+2;num=t;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++){
		f[i]=1;
		for(int j=1;j<i;j++){
			if(a[j]<=a[i])f[i]=max(f[i],f[j]+1);
		}
		mx=max(mx,f[i]);
	}
	cout<<mx<<"\n";int id1=-1,id2=-1;
	for(int i=1;i<=n;i++){
		if(f[i]==1)add(s,i,1);
		if(f[i]==mx)add(i+n,t,1);
		if(i==n&&f[i]==mx)id2=tot;
		add(i,i+n,1);
		if(i==n)id1=tot;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(a[j]<=a[i]&&f[i]==f[j]+1)add(j+n,i,1);
		}
	}
	cnt=dinic();cout<<cnt<<"\n";
	if(n==1){
	    cout<<"1\n";return 0;
	}
	cost[2]=inf,cost[3]=0;cost[4]=inf,cost[5]=0;
	if(id1!=-1)cost[id1]=0,cost[id1^1]=inf;
	if(id2!=-1)cost[id2]=0,cost[id2^1]=inf;
	cout<<cnt+dinic();
}

航空路线问题

注意到双向道路,从西到东等价于从东到西,所以原题化为求出两条起点终点分别为 1,n 的互不相交(点不交)的路径。

How?注意到网络流当流量恒为1时其等价于找一条 1n 的路径,这里找两条路径,则流量恒为2,每条边容量恒为1,这样就能跑出若干路径。

但怎么限制途径城市最多呢?用费用来限制。

拆点,将每一个城市(除起终点)拆为 i,i+n,连边 (i,i+n,1,1)。同时将原图中的无向边按照由西到东的顺序建立,由出点到入点,容量无穷(边可以经过无数次,虽然点不能重复经过严格强于边不能重复经过)。

然后跑网络流。方案的话,顺着残量网络跑dfs,一次只找一条路径,然后把流量减掉,找两条合并输出即可。

map<string,int>h;
string a[N];
void init(){
	cin>>n>>m;s=n+n+1,t=s+1,num=t;
	for(int i=1;i<=n;i++){
		cin>>a[i];h[a[i]]=i;add(i,i+n,1+(i==1||i==n),-1);
	}
	for(int i=1;i<=m;i++){
		string x,y;cin>>x>>y; 
		int u=h[x],v=h[y];
		if(u>v)swap(u,v);
		add(u+n,v,inf,0);
	}
	add(s,1,2,0);add(n+n,t,2,0);
	zkw(-1);
}
int ans[N],dcc;
bool dfs(int u){
	if(u==s)return true;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(i&1){
			if(val[i]>0&&dfs(v)){
				if(u<=n)ans[++dcc]=u;
				--val[i];	
				return true;
			} 
		}
	}
	return false;
}
signed main(){
	ios::sync_with_stdio(false); 
	init();
	dfs(t);
	for(int i=1;i<=dcc;i++)cout<<a[ans[i]]<<"\n";
	dcc=0;
	dfs(t);
	for(int i=dcc-1;i;--i)cout<<a[ans[i]]<<"\n";
}

数字梯形问题

承接上题,这是一道综合三类路径问题。

首先,我们知道:点不可重复严格强于边不可重复,同样的,我们进行拆点,连接 (i,i+n)

以下简称原图中有的边实边,(i,i+n) 为虚边

然后我们考虑点不可重复经过,这意味着虚边 (i,i+n) 容量为 1

考虑边不可重复经过,则实边容量为1,虚边容量无限大

考虑点,边均可重复经过,将虚实边的容量全部设置为无限大即可。

火星探险问题

首先明确本题的考察内容。对于基础地图的处理是简单的,拆出入点即可,我们主要就解决岩石标本的问题。

注意到岩石标本只能被采集一次,但换个角度,对于当前格点而言,有无岩石是两个不同的状态

我们可以对于一个点,将其拆为出入点,用连接入点和出点的不同边表示走过当前节点的不同状态/决策

也即建立费用、流量不同的重边

对于走地图问题,流量一般表示为走过的次数(容量为次数上限),而费用一般表示为收益/代价

所以,我们可以在有岩石的格点,专门建立一条边表示采集岩石这个决策,收益为1,次数上限为1。

也即容量为1,费用为1.

最后跑最大费用流即可。

可恶的是输出方案,可以考虑跑残量网络每一次暴力找路径

bool dfs(int u){
	if(u==s)return true;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];if(i%2==0)continue;
		if(val[i]>0){
			if(dfs(v)){
				if(abs(u-v)!=n*m&&u!=t)ans[++dcc]=u;
				--val[i];return true;	
			}
		}
	}
	return false;
}

深海机器人问题

和上题一样,但本题更加明显。对于一个点向东/北走,本质上对应了两种决策,而路径上的标本即为收益,后续路径即为决策后效性。

这题的决策体现在边上,就无需拆点了。

那么,由于标本只能被采集一次,我们仍然可以对两个相邻的点连两条边,一条边表示采集标本,容量1,费用为标本价值。而另一条边表示路过这个决策,容量inf,费用0.

最后跑最大费用流。

注意在这两个走地图问题中,有一个不易发现的套路。

只用费用表收益/决策,保证本地图满流。否则费用流就会受到最大流限制。

void init(){
	int c1,c2;cin>>c1>>c2;
	cin>>n>>m;n++,m++;
	for(int i=1;i<=n;i++)for(int j=1;j<m;j++)cin>>a[i][j];
	for(int i=1;i<=m;i++)for(int j=1;j<n;j++)cin>>b[j][i];
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)id[i][j]=++num;
	//技巧:建重边,费用不同 
	s=num+1,t=num+2;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int x=i,y=j;
			if(x!=n){
				++x;
				add(id[i][j],id[x][y],1,-b[i][j]);
				add(id[i][j],id[x][y],inf,0);
			}
			x=i,y=j;
			if(y!=m){
				++y;
				add(id[i][j],id[x][y],1,-a[i][j]);
				add(id[i][j],id[x][y],inf,0);
			}
		}
	}
	for(int i=1;i<=c1;i++){
		int cnt,x,y;cin>>cnt>>x>>y;x++,y++;
		add(s,id[x][y],cnt,0);
	}
	for(int i=1;i<=c2;i++){
		int cnt,x,y;cin>>cnt>>x>>y;x++,y++;
		add(id[x][y],t,cnt,0);
	}
	num=t;
	zkw(-1);
}

汽车加油行驶问题

这是分层图的网络流问题,主要可以学到的是抽象状态的能力。

注意到在本题中,多了一个油量限制,这是独立于容量,费用之外的限制。对于这种东西,我们考虑用动态规划的思想去刻画状态。

我们将流量也视作一个状态,结合最短路,建立流量分层图即可。

int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1},id[105][105][15],a,b,c,k,S[105][105];
signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>k>>a>>b>>c;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>S[i][j];
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int p=0;p<=k;p++)id[i][j][p]=++num;
	s=++num,t=++num;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(S[i][j]){
				for(int p=0;p<=k;p++){
					add(id[i][j][p],id[i][j][k],1,a);
				}
				for(int p=0;p<4;p++){
					int x=i+dx[p],y=j+dy[p];
					if(x<1||y<1||x>n||y>n)continue;
					add(id[i][j][k],id[x][y][k-1],1,b*(p&1));//分层
				}
			}
			else {
				add(id[i][j][0],id[i][j][k],1,a+c);
				for(int p=k;p;--p){
					for(int q=0;q<4;q++){
						int x=i+dx[q],y=j+dy[q];
						if(x<1||y<1||x>n||y>n)continue;
						add(id[i][j][p],id[x][y][p-1],1,b*(q&1));
					}
				}
			}
		}
	}
	add(s,id[1][1][k],1,0);
	for(int i=0;i<=k;i++)add(id[n][n][i],t,1,0);
	zkw(1);
	return 0;
}

网络流 24 题——Trick 总括篇

  1. 网络流解决最大/小费用问题,要么最小割最大流,要么最小费用流

最小费用流的前提是最大流,所以在有一些点可以不取换最优代价的时候,是不可行的。

当每个点都只能取一次的时候,可以考虑费用化为容量,求最小割/最大流。

  1. 最大权闭合子图模型
  2. 将一个点的不同状态拆分,化为左右部,分别求解
  3. 部分计算,合并答案
  4. 正难则反,拆分——>合并
  5. 路径的过程过来,变路径
  6. 网络流的方案输出一般体现在满流边和残量网络上,当然也可以像动态规划一样记录上一个点
  7. 区间操作问题,我们仍然可以将其抽象为(亦或者说微观化?)序列元素操作问题
  8. 在流网络中,加边的后果就是导致分流
  9. 化整为零,化零为整
  10. 在数轴上,我们选择扩域,将每个点扩两个域,中间部分用来表示该点,类似于拆点连边
  11. 转化新旧两个状态,等价于给出旧的,收回新的,借助其他点转化,而非直接转化
  12. 在网络流题中,对于同一物品的不同状态,不同决策,一般可以分为两种处理办法
  1. 拆点,将不同状态拆开,并进行不同限制 ,用点作为决策主体
  2. 建立“重边”,用边来体现不同决策
  1. 流量恒为1的网络流问题,等价于最短路问题
  2. 一个流的意义是一条路径,一条路径可以被赋予多重意义。
  3. 网络流的转移,有些类似于动态规划。对于动态规划的不同状态点连边,也可以化为网络流问题。(多个终态。若只有一个终态则可以简化为最短路问题)
  4. 点不可重复严格强于边不可重复
  5. 对于走地图问题,流量一般表示为走过的次数(容量为次数上限),而费用一般表示为收益/代价
  6. 用动态规划的思想去刻画状态
posted @   spdarkle  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示