网络流 最大流 SAP 模板

一言不合就安利——

        首先安利一个神奇的网站 里面的网络流的网址 其中其他东西也挺有用

        至于怎么找到的呢 我才不会说是当初搜网络流一个个都不理解然后翻到十几页点进去看到的

        呐 貌似是 国立台湾师范大学 的网址 (这学校是不是这个名=-=繁体我不会认啊好尴尬)

前言——

        作为一个刚学网络流 就找SAP的四维生物——Frocean 感觉这网络流真是个玄学的东西=-=

        跟着标打模板 0分 0分 0分...... 然后发现建图建反向边权值要为0

        然后 90分 90分 90分...... 一直TLE一个点 于是打上了读入优化

        依旧 90分 90分 90分...... 经过多模板融合推敲 求同存异 最后改动了一些 提交 得68ms/4.5MB

        然后我那叫一个开心啊 就差拉车载个体面的先生去东安市场 然后去城里最好的饭摊上 吃热烧饼夹爆羊肉了

        之后继续优化 开O2 得48ms/4.5MB 开心~

        于是建议大家抄标后慢慢看 多抄几次慢慢就理解了 而且网上那些dalao们都说这类题目重在建图 不过理解一下还是比较好

        顺便建议大家学学神奇的 HLPP(Highest-Label Preflow Push)最高标号预流推进算法

        虽然长长的 似乎比树剖还长 但如果毒瘤出题人要在复杂度上和你死纠 你还是得用(可能性极小嗯)

        不过不管那么多了 本文直接上SAP代码 概念什么的麻烦不想搬 网上的概念还是比较正规的

        个人认为 网络流的概念 只是跟你介绍构图之类的而已 我认真地看来看去还是不会打(应该是我悟性问题吧)

        而且由于悟性问题 有些地方还是不太清楚 解释模糊 见谅=-= (但为什么不清楚的地方都是关键的地方啊啊啊 NG)

代码—— 题目依然是洛谷的模板 比较正常的代码见下

#include <iostream>
#include <cstdio>
using namespace std;
const int MAX = 100005;
struct Edge { //(下行) w 会因为寻找增广路时改变权值 因此存的是边权 但更形象地说存的是剩下的流量
	int to,next,w;//存边是一条边一条反向边 使用时没有区别 但更新流的时候要改变剩余流量w 查询可以是 a 和 a+1 或者 a 和 a^1 当然后者更快
} edge[MAX << 1];//(下行) gap[x]=y 意为 此时深度为 x 的节点有 y 个 cur相当于first数组 只是取next里的数作新的first 从路中间开始走而不是头
int first[MAX],gap[MAX],dep[MAX],cur[MAX];//first邻接表机制 gap和cur分别是断层优化和当前弧优化 dep存点深度
int n,m,i,j,tot = 1;//本模板为了用异或修改反向边权值 tot从1开始 即第一条边存在edge[2]里 如果用+1找反向边不用初始定义 但找边会慢一些
void add(int x,int y,int z)//邻接表备注打太多遍了不想打 大家无聊就移步去我的别的文章看吧 别的没有就再移2333
{
	edge[++tot].next = first[x];
	edge[tot].to = y;
	edge[tot].w = z;
	first[x] = tot;
	edge[++tot].next = first[y];
	edge[tot].to = x;/*
	edge[tot].w = 0;*///本行可略 可爱的C++已经帮你初始定义为0了 但如果你为了缩行美观(两行 一行四句) 还是加上比较好 当然换下顺序不加也美观
	first[y] = tot;
}
int dfs(int p,int mx)
{
	if (p == j) return mx;//当前点的流量流到汇点了 返回流量(网上说是流完 应该是流完路径 即流到尾的意思吧)
	int mxflow = 0;//记录某增广路的最大流 第一次进入该子程序则就是记录最大流 先赋值为0
	for (int a = cur[p] ; a ; a = edge[a].next)//遍历与p点相连的边 除去了部分无用的边 如果不优化只用改成a=first[p] 再把其他cur行去掉
		if (edge[a].w && dep[edge[a].to] + 1 == dep[p])//(第一个判断) 当第a条边还能流 即(可能)有增广路
		{//(上句 第二个判断) 边的两端的点深度只相差1 即该边不是跨层的 则执行命令
			cur[p] = a;//当前弧优化 去除前面用过/无用的边
			int flow = dfs(edge[a].to,min(mx - mxflow,edge[a].w));//深搜 找该边连下去的边 直到汇点
			mxflow += flow;//上句flow记录某一增广路最大值 flow相当于子程序中的mxflow 此处累计增广路值
			edge[a].w -= flow;//当前边减去流量 剩下的用在别的增广路上 继续搜
			edge[a ^ 1].w += flow;//当前边反向弧增加流量 用在找别的增广路上的流时改变流向 (这里解释的话有点复杂 要翻概念=-=)
				if (mxflow == mx) return mxflow;//当前深度的流全部流完了 直接闪退返回最大流
		}
	cur[p] = first[p];//还原边(这子程序不是一个for就完了的 没看见子程序名字叫dfs么 for里还有深搜呢) 防止退出某层程序后有些有用的边没搜到
		if (!--gap[dep[p]]) dep[i] = n;//减去1单位的当前深度节点数量 加 判断断层 因为下句(Tip:本句和下句顺序不能换 亲测100分和0分)
	++gap[++dep[p]];//(gap里面)将源点深度变为与他相连的最小深度的点+1 方便其他增广路经过他 (gap外面)增加1单位的当前深度节点数量 因为上句
	return mxflow;//返回当前增广路的最大流
}
int main()
{
	int x,y,z;//里应外合的定义
	scanf("%d%d%d%d",&n,&m,&i,&j);//安步当车的输入
	for (int a = 1 ; a <= m ; a++)//循规蹈矩的循环
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);//邻接表存边 注意反向边初值为0
	}
	tot = 0;//懒得定义新变量直接用tot  ↓(这是箭头)<---tot是用来存每次找到增广路的值的
	gap[0] = n;//所有节点的深度初始为0 ↓(箭头是上行的)<----(这个箭头是本行的)n表节点个数
		for ( ; dep[i] < n ; tot += dfs(i,1 << 30));/*此处可不取1<<30 只要取比最大流大的数就好 但99%的时候你不知道最大流是多少
		while (dep[i] < n) tot += dfs(i,1 << 30);*///本行与上行等价 怕诸君看不懂=-= 本句用来找深(长)度一样的增广路 然后长度递增
	printf("%d\n",tot);//当机立断的输出
	return 0;//逢考必备的结尾
}

其实SAP挺短的呢=w=就是要记的细节多

然后优化见下 (也只是普通的getchar和位运算之类而已) 就不贴注释啦

Tip: 异或清零不要乱用 不然要出毛病=-= (或许又是我的原因?) 然后本代码巨丑(/▽\)

#include <iostream>
#include <cstdio>
#define ten(a) (a<<(1<<1|1))+(a<<1)
#define re register
using namespace std;
const int asc = (1<<1|1) << (1<<(1<<1));
const int MAX = 3125<<5|1;
struct Edge {
	int to,next,w;
} edge[MAX<<1];
int first[MAX],gap[MAX],dep[MAX],cur[MAX];
int n,m,i,j,tot=1<<1;
inline void r(re int &x) {
	re char q = getchar(); x = 0;
	while ('0'>q||q>'9') q=getchar();
	while ('0'<=q&&q<='9') x=ten(x)+q-asc,q=getchar();
}
void add(re int x,re int y,re int z) {
	edge[tot].next=first[x]; edge[tot].to=y; first[x]=tot; edge[tot++].w=z;
	edge[tot].next=first[y]; edge[tot].to=x; first[y]=tot++;
}
int dfs(int p,int mx) {
	if (p==j) return mx;
	int mxf=0;
	for (int a=cur[p]; a; a=edge[a].next)
	if (edge[a].w&&dep[edge[a].to]+1==dep[p]) {
		cur[p]=a;
		int f=dfs(edge[a].to,min(mx-mxf,edge[a].w));
		mxf+=f;
		edge[a].w-=f;
		edge[a^1].w+=f;
		if (mxf==mx) return mxf;
		}
	cur[p]=first[p];
	if (!--gap[dep[p]]) dep[i]=n;
	++gap[++dep[p]];
	return mxf;
}
int main() {
	r(n),r(m),r(i),r(j);
	re int x,y,z;
	for (re int a=1; a<=m; a++) {
		r(x),r(y),r(z);
		add(x,y,z);
	}
	tot=0;
	gap[0]=n;
	re int mx=1<<20;
	for (; dep[i]<n; tot+=dfs(i,mx));
	printf("%d\n",tot);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/frocean/article/details/80917188