图论-网络流⑤-最大流解题②

图论-网络流⑤-最大流解题②

上一篇:图论-网络流④-最大流解题①

下一篇:图论-网络流⑥-费用流①

参考文献:

暂无

大纲

  • 什么是网络流
  • 最大流(最小割)
  • D i n i c Dinic (常用)
  • E K EK
  • S a p Sap
  • F o r d F u l k e r s o n Ford-Fulkerson (不讲)
  • H L P P HLPP (快)
  • 最大流解题 Start \color{#33cc00}\texttt{Start} End \color{red}\texttt{End}
  • 费用流
  • S p f a Spfa 费用流
  • B e l l m a n F o r d Bellman-Ford 费用流
  • D i j k s t r a Dijkstra 费用流
  • z k w zkw 费用流
  • 费用流解题
  • 有上下界的网络流
  • 无源汇上下界可行流
  • 有源汇上下界可行流
  • 有源汇上下界最大流
  • 有源汇上下界最小流
  • 最大权闭合子图
  • 有上下界的网络流解题

上篇中讲解了最大流解题当中的两道经典例题,这一篇会讲进阶的网络最大流解题。

拍照

最小割经典例题,简讲。

s s 连每次拍照,流量为获益。每个下属连 t t ,流量为交费。每次拍照连相关下属,流量为无穷。用最小割思想,要么牺牲收益,要么交费,答案为收益总和 - 网络流图最小割。

[网络流24题]试题库问题

难度在于输出方案,需要掌握网络流的精髓。

只有你知道网络流每条边是什么意思,这题没有难点。蒟蒻就放一下输出方案部分的代码吧:

for(int i=1;i<=k;i++){
	printf("%d:",i);
	for(int j=g[i+tmp+1];j;j=e[j].nex){
		int to=e[j].adj;
		if(to>1&&to<=tmp+1&&e[j].fw)
			printf(" %d",to-1);
	}
	puts("");
}

奶牛隐藏

你现在有 5 5 分钟的看题时间和 2 2 分钟的谔谔时间。(我保证这题就是网络最大流题)[提示]


如果你实在做不出来,看了提示后可能恍然大悟。二分是个重要的思路,但这题怎么二分呢?首先这里的道路长度是不可以直接利用的, F l o y d Floyd 求一下节点两两直接的最短路,然后二分时间,如果两个节点可以走到,连边(因为有初始结束状态,所以拆点),然后跑普通的最大流。

整理一下:

二分时间 m i d t midt
s s 向每个牛棚的一号点连流量为牛数的边
每个牛棚的二号点向 t t 连流量为牛棚容量的边
对于每两个点 i , j i,j ,如果 d i s [ i ] [ j ] dis[i][j] 小于等于 m i d t midt d i s dis F l o y d Floyd 出来的最短路数组, d i s [ x ] [ x ] = 0 dis[x][x]=0 ),就由 i i 的一号点连向 j j 的二号点,流量为 \infty
如果最大流 = = 总牛数,那么答案 m i d t \le midt 。否则,答案 m i d t \ge midt

蒟蒻的代码:

#include <bits/stdc++.h>
using namespace std;
#define lng long long
const int N=5e2;
const int M=1e5;
const lng R=1e12;
const lng inf=1e16;
int n,m,s,t;
lng res,ans,dis[N][N];
struct G{
	struct edge{
		int adj,nex;
		lng fw;
	}e[M];
	int g[N],top;
	G(){top=1;}
	void add(int x,int y,lng z){
		// printf("%d-%d %d\n",x,y,z);
		e[++top]=(edge){y,g[x],z};
		g[x]=top;
	}
}O,D;
struct side{
	int d; lng tim;
};
vector<side> es;
int dep[N],cur[N];
bool vis[N];
queue<int> Q;
bool bfs(){
	for(int i=1;i<=n;i++)
		vis[i]=0,cur[i]=D.g[i];
	Q.push(s),vis[s]=1,dep[s]=0;
	while(Q.size()){
		int x=Q.front(); Q.pop();
		for(int i=D.g[x];i;i=D.e[i].nex){
			int to=D.e[i].adj;
			if(!vis[to]&&D.e[i].fw){
				vis[to]=1;
				dep[to]=dep[x]+1;
				Q.push(to);
			}
		}
	}
	return vis[t];
}
lng dfs(int x,lng F){
	if(!F||x==t)
		return F;
	lng flow=0,f;
	for(int i=cur[x];i;i=D.e[i].nex){
		int to=D.e[i].adj; cur[x]=i;
		if(dep[x]+1==dep[to]&&
		(f=dfs(to,min(F,D.e[i].fw)))>0){
			D.e[i].fw-=f;
			D.e[i^1].fw+=f;
			flow+=f,F-=f;
			if(!F) break;
		}
	}
	return flow;
}
bool enough(lng T){
	D=O,ans=0;
	for(auto i:es)
		if(i.tim>T)
			D.e[i.d].fw=0;
	while(bfs()) ans+=dfs(s,inf);
	return ans>=res;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		lng x,y;
		scanf("%lld%lld",&x,&y);
		res+=x;
		O.add(1,i+1,x);
		O.add(i+1,1,0);
		O.add(i+n+1,2*n+2,y);
		O.add(2*n+2,i+n+1,0);
		for(int j=1;j<=n;j++)
			if(i!=j) dis[i][j]=inf;
	}
	for(int i=1,a,b;i<=m;i++){
		lng c;
		scanf("%d%d%lld",&a,&b,&c);
		dis[a][b]=min(dis[a][b],c);
		dis[b][a]=dis[a][b];
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				dis[i][j]=min(dis[i][j],
				dis[i][k]+dis[k][j]);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			O.add(i+1,j+n+1,res);
			es.push_back((side){O.top,dis[i][j]});
			O.add(j+n+1,i+1,0);
		}
	}
	s=1,n=t=2*n+2;
	lng l=0,r=R+1;
	while(l<r-1){
		lng mid=(l+r)>>1;
		if(enough(mid)) r=mid;
		else l=mid;
	}
	if(r==R+1) puts("-1");
	else printf("%lld\n",r);
	return 0;
}

总结:想到了用二分就解决了题目的一半

[CTSC1999]家园 / 星际转移问题

数据范围小得很,如果你觉得简单就谔谔吧。


这题是拆点的终极例题——时间拆点。因为太空船的运输是考时间组织的,所以把每个太空站(包括地月)拆成最大时间个点。同上题,二分时间来拆点。太空船就转化为不同太空站(包括地月)不同时间之间的边,同个太空站相邻时间之间连流量为 \infty 的边。

整理一下:

二分时间 m i d t midt
s s 向地球的第 0 0 时间连流量为 k k 的边。
每个太空站(包括地月)前一时间向后一时间连流量为 \infty 的边。
对于每个太空船的每一时刻,由它现在应该在的位置的该时间点节点向下一时间它应该在的位置的下一时间点节点连流量为太空船容量的边。
月球的第 m i d t midt 时间向 t t 连流量为 k k 的边。
如果最大流 = k =k m i d t midt 减半;否则 m i d t midt 增半。

//750s*15=10500
#include <bits/stdc++.h>
using namespace std;
const int V=10510;
const int M=6e4;
const int inf=1e8;
int n,m,k,s,t,p,fans,h[22],cnt[22],sp[22][16];
class Graph{
public:
	int top,g[V],to[M],fw[M],nex[M];
	void clear(){memset(g,0,sizeof g),fans=0,top=1;}
	void add(int x,int y,int f){nex[++top]=g[x],to[top]=y,fw[top]=f,g[x]=top;}
	void Add(int x,int y,int f){add(x,y,f),add(y,x,0);}
};
class Dinic:public Graph{
	int dep[V],cur[V]; bool vis[V]; queue<int> q;
	bool bfs(){
		for(int i=1;i<=p;i++) vis[i]=0,cur[i]=g[i];
		q.push(s),vis[s]=1,dep[s]=0;
		while(q.size()){
			int x=q.front();q.pop();
			for(int i=g[x];i;i=nex[i])if(!vis[to[i]]&&fw[i])
				q.push(to[i]),dep[to[i]]=dep[x]+1,vis[to[i]]=1;
		}
		return vis[t];
	}
	int dfs(int x,int F){
		if(x==t||!F) return F;
		int f,flow=0;
		for(int&i=cur[x];i;i=nex[i])
			if(dep[to[i]]==dep[x]+1&&(f=dfs(to[i],min(F,fw[i])))>0)
				{fw[i]-=f,fw[i^1]+=f,F-=f,flow+=f;if(!F) break;}
		return flow;
	}
public:
	void dinic(){while(bfs()) fans+=dfs(s,inf);}
}net;
bool check(int x){
	p=t=(n+2)*(x+1)+2,s=t-1;
	net.clear();
	net.Add(s,(n+1-1)*(x+1)+1,k),net.Add((n+2-1)*(x+1)+x+1,t,k);
	for(int i=1;i<=(n+2);i++)
		for(int j=1;j<=x;j++)
			net.Add((i-1)*(x+1)+j,(i-1)*(x+1)+j+1,inf);
	for(int i=1;i<=m;i++){
		for(int j=1;j<=x;j++){
			int now=sp[i][(j-1)%cnt[i]+1],to=sp[i][(j+1-1)%cnt[i]+1];
			// printf("%d->%d\n",now,to);
			net.Add((now-1)*(x+1)+j,(to-1)*(x+1)+j+1,h[i]);
		}
	}
	net.dinic();
	return fans>=k;
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++){
		scanf("%d%d",h+i,cnt+i);
		for(int j=1;j<=cnt[i];j++){
			scanf("%d",&sp[i][j]);
			if(sp[i][j]==0) sp[i][j]=n+1;
			if(sp[i][j]==-1) sp[i][j]=n+2;
		}
	}
	int l=0,r=751;
	while(l<r-1){
		int mid=(l+r)>>1;
		if(check(mid)) r=mid; else l=mid;
	}
	if(r==751) puts("0");
	else printf("%d\n",r);
	return 0;
}

总结:三维生物的思考不能被维度所限制

还有些相通的例题,会在后面的费用流解题中讲解。下篇会开始讲费用流的知识。

祝大家学习愉快!

发布了26 篇原创文章 · 获赞 58 · 访问量 7623

猜你喜欢

转载自blog.csdn.net/KonnyWen/article/details/104347534