相关概念

网络流(似水流)(整张图流出的水流) 网络流的本质是水流问题 源点:水流出的点(只有一个) 汇点:水流进的点(只有一个) 弧(边):水流容量(用小c表示) 可行流:一条路径(s ==> t)和(此路径能流过的最大水流)【可行流 = 路径边 + 能流过的最大水流】 可改进路(增广路):找到一条可行流 使原流量增加,可走反边增广

残留网络(增广时 用):剩余容量+反向平衡的流量 割切(割):找到一种方法,将源和汇断开,所有被切断的边 即边集 称为割 断的每条边,一定是S到T中每条可行流的其中一条边 割边容量:被切断边的边权【管道容量】之和(用大C表示) 最小割:割边容量最小

把整张图抽象成一根各处直径不相同的管道(波浪形的管道),S为起点,T为终点,那么管道中的最小直径就是最小割,也是最大流 最大流=最小割

最大流

FF方法(最大流算法的本质):

对网络流为0的图找增广 再在残量网络里找增广(一直下去,直到无法增广) 各路算法都是在写怎么增广(找增广路) EK算法: bfs增广,复杂度太高,一般不用

sap

时间复杂度 O(n^3) 【每个点访问了一次;每个点也都可能进入每一层;每个点都要遍历一遍其他的点,看是否有边】【所以是 n(点) * n(层) * n(点)】

网络流一般为稠密图(指边稠密) 网络流的增广=增流量

流不了然后将那一个的点提层 提一级之后,其实走的是残留网络里的增广路

本质:以动态最短路【因为要提层,所以是动态的】的方式枚举所有的路(S==>T),即 所有的动态最短路加起来,就是所有可行流(即 所有从S到T的路) 在枚举过程中,同时也遍历了可行流,最后回溯求出了最大流

代码:

int s,t;  //s为源 //t为汇 
int d[N],cntd[N]; //d[i]=x表示i点在第x层 //cntd[i]表示i层的点数 
int dfs(int x,int flow){ //x表示点x  //flow表示流入x的流量 //dfs求的是从x可以流出的流量 即x流向终点的流量
	if(x==t) return flow;  //如果x==t,返回流量 
	int sum=0; //sum为从x流出的流量 
	for(int i=1;i<=n;++i){ //遍历点 
		if(e[x][i]&&d[x]==d[i]+1){ //i为x的下一层 
			int tmp=dfs(i,min(e[x][i],flow-sum)); //tmp表示通过此边 x->i 的流量 【通过此边从x流向i的流量】 //tmp表示通过这条边流向终点地流量 
			e[x][i]-=tmp;
			e[i][x]+=tmp; //加反边
			sum+=tmp;
			if(sum==flow) return sum; //流入的水都流出了,就return 
		}
	}
	if(d[s]>=n) return sum; //d[s]>=n表示有断层 
	cntd[d[x]]--;
	if(cntd[x]==0) d[s]=n; //cntd[x]==0说明d[x]层断层了,所以打一个d[s]=n的标记【表示断层的含义】,即相当于fg的作用
						   //s最多只到n-1层【t始终在0层】,所以n为d的不可能取值 
	d[x]++;
	cntd[d[x]]++;
	return sum; 
}

Dinic

O(n^n^n)【实际上,它的复杂度比spa优秀一点】 求一次最短路,求一次dfs(即求增广) 如此往复,直至不能增广

每一次dfs都是增广一大把(即找到一大把可行流)

从源点开始做一次bfs(求定标)(找s==>t的最短路,求出来的定标就是到s的最短路) 一次bfs可以更新所有定标

最大流不可能同时卡spa和Dinic 前向星处理重边问题很优秀 奇数^1==偶数(==奇数-1) 偶数^1==奇数(==偶数+1)

int d[N];
queue<int>q;
bool bfs(int s,int t){ //bfs在残量网络里跑最短路,搜顶标
	memset(d,0,sizeof d);
	while(!q.empty()) q.pop(); 
	d[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		if(u==t) return 1;  //搜到了终点,return 
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].a;
			if( d[v]==0 && e[i].len ){  //d==0可以表示还没访问过 
				d[v]=d[u]+1;
				q.push(v);
			}
		}
	}
	return 0;
}
int dfs(int x,int flow,int t){  //dfs求的是从x可以流向终点的流量
	if(x==t) return flow;
	int sum=0;
	for(int i=head[x];i;i=e[i].next){
		int y=e[i].a;
		if(d[x]+1==d[y]&&e[i].len){
			int tmp=dfs(y,min(flow-sum,e[i].len),t);
			e[i].len-=tmp;
			e[i^1].len+=tmp;  //加反边
			sum+=tmp;
			if(sum==flow) return flow;
		}
	}
	return sum;
}
int main(){
	int ans=0;
	while(bfs(s,t)){ //bfs搜顶标 
		ans+=dfs(s,INF,t);
	}
	return 0; 
}

spa和Dinic: spa:从i点到t的最短路,即 从t开始的最短路 Dinic:从s开始的最短路,即从i到s的最短路

最小费用最大流(保证流最大的情况下费用最小)

w:单位流量费用

退流时要还钱 则 会有原先边权的相反数 即 spfa

求最大流时: dfs优先增广费用小的(最短路) 全部增广完时,求出的最大流就是最小费用

int cnt,head[N];
struct node{
	int a,cap,next,ct,fw;  //cap表示管道容量,ct(cost)表示单位流量费用,fw(flow)表示流量 
}e[M<<1];  //ct的作用就是找最短路时和计算答案,其他地方用不着 
void add(int x,int y,int cap,int ct) {
	e[cnt].a=y;
	e[cnt].cap=cap;
	e[cnt].ct=ct;
	e[cnt].fw=0;
	e[cnt].next=head[x];
	head[x]=cnt++
}

int dis[N],pre[N]; //pre[x]=i表示x前驱的那条边【即前驱边】 在前向星中的标号为i 
//设x的前驱为u,则pre记录的是u->x此边,且它记录的是最小费用的边 
bool vis[N];  //是否在队列中 
bool spfa(int s,int t){
	queue<int>q;
	for(int i=0;i<n;++i){
		dis[i]=INF;vis[i]=0;pre[i]=-1;
	}
	dis[s]=0;
	vis[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i!=-1;i=e[i].next){   
			int v=e[i].a;
			if( dis[u]+e[i].ct<dis[v] && e[i].cap>e[i].fw ){  //花费地更少 且 还有容量可以流水,就更新 
				dis[v]=dis[u]+e[i].ct;
				pre[v]=i;  //记录前驱边 
				if(vis[v]==0){  //如果它不在队列,就让它入队
					q.push(v); 
				}
			}
		}
	}
	if(pre[t]==-1) return 0;  //表示求最短路时,没有更新到t,则无可行流了 
	return 1; 
}

//返回的是最大流,ct存的是最小费用 
int solve(int s,int t,int& ct){  //求的是s到t的最大流 或者 s可以流出的流量【最大的】 
	int fw=0;
	cost=0;
	while(spfa){  //每次用最短路找一条费用最小的可行流 
		int minx=INF; //minx是spfa找出可行流的最大流,即残量网络可行流中的最小管道容量
		for(int i=pre[t];i!=-1;i=pre[e[i^1].a]){  //反向边的终点就是原边的起点 或者说 反向边的终点就是原点的前驱 
			minx=min(minx,e[i].cap-e[i].fw);
		}
		for(int i=pre[t];i!=-1;i=pre[e[i^1].a]){
			e[i].fw+=minx;
			e[i^1].fw-=minx;
			ct+=e[i].ct*minx; 
		}
		fw+=minx; 
	}
	return fw;
} 

int main(){
	memset(e,-1,sizeof e);
	memset(head,-1,sizeof head);
	add(a,b,c,d);add(b,a,0,-c);
	sovle(s,t,ans);
	return 0;	
}