网络流与最大流与二分图

其实本质上二分图的效果和阉割版的网络流差不多所以大部分代码都可以用网络流实现。时间上也差不了多少的,然后就是说二分图实现会简单很多咯。
先说说网络流吧,证明与思路往后放,重要的是最大流就等于最小割。指在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除,就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量。这个是极其重要的一个结论与应用,大部分的题目都与其有关吧。

比如说啊就像网络流24题中的一道方格取数(https://www.luogu.com.cn/problem/P2774),一般人是很难想到网络流的我也没有,只不过看到才写的,本质而言就是将图化成黑白图,就可以转化成二分图,然后如果以值来建边的话就可以求最小割然后再让总和减去其,即为答案。

还有就是分层图嘛,就看这个呗(https://blog.csdn.net/qq_40736036/article/details/85041838)或一个大佬总结的所有模板也可以(https://www.cnblogs.com/birchtree/p/12912607.html),分层图的话就是多维的时候啊,将本来自己的点的值继承下去呗然后再搞。
然后就到一些定义:这是oiwiki的(https://oi-wiki.org/graph/flow/)这是不知道是哪位的,转载的://(https://blog.csdn.net/A_Comme_Amour/article/details/79356220)
然后说说我的理解,就是一次又一次的用bfs来找可通过的路径,然后用拓扑做好记号,使用dfs来流,在其中流过的边建立反向边 ,做完这些之后时间复杂度近优秀,且保证每次都是最大流。
给一下luogu模板吧:P3376 【模板】网络最大流(https://www.luogu.com.cn/problem/P3376)就这东西:
代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int len=0;
int last[50000];
int ST,ED;
int q[50000];
bool v[50000];	
int h[50000];
struct pp
{
	int x,y,c,next,other;
};
pp p[50000];
void ins(int x,int y,int c)
{
	len++;
	int now1=len;
	len++;
	int now2=len;
	p[now1]={x,y,c,last[x],now2};
	last[x]=now1;
	p[now2]={y,x,0,last[y],now1};
	last[y]=now2;
	return ;
}

	
bool bfs(int s,int t)
{
	memset(h,0,sizeof(h));
	h[s]=1;
	int st=1,ed=2;
	q[st]=s;
	while(st!=ed)
	{
		int x=q[st];
		for(int i=last[x];i!=-1;i=p[i].next)
		{
			int y=p[i].y;
			if(h[y]==0&&p[i].c>0)
			{
				h[y]=h[x]+1;
				q[ed]=y;
				ed++;
			}
		}
		st++;
	}
	return h[t];
}
int dfs(int x,int f)
{
	if(x==ED) return f;
	int flow=0;
	for(int i=last[x];i!=-1;i=p[i].next	)
	{
		int y=p[i].y;
		if(h[y]==h[x]+1&&p[i].c>0&&f>flow)
		{
			int t=dfs(y,min(p[i].c,f-flow));
			flow+=t;
			p[i].c-=t;
			p[p[i].other].c+=t;
		}
	}
	if(flow==0) return h[x]=0;
	return flow;
}
signed main()
{
	memset(last,-1,sizeof(last));
	scanf("%lld%lld%lld%lld",&n,&m,&ST,&ED);
	for(int i=1;i<=m;i++)
	{
		int x,y,c;
		scanf("%lld%lld%lld",&x,&y,&c);
		ins(x,y,c);		
	}
	int ans=0;
	while(bfs(ST,ED)) ans+=dfs(ST,1e9);
	printf("%lld\n",ans);
	return 0;
}

会有些坑点比如说我的代码中通常len每次要等于零哈,和last [ ] =-1也要记一下每次h也要清空,然后重点就是dfs的那一部分:
bfs注意点:

bool bfs(int s,int t)
{
	memset(h,0,sizeof(h));
	h[s]=1;//记住最好从1开始不然可能0的话可能有锅
	int st=1,ed=2;
	q[st]=s;
	while(st!=ed)
	{
		int x=q[st];
		for(int i=last[x];i!=-1;i=p[i].next)
		{
			int y=p[i].y;
			if(h[y]==0&&p[i].c>0)//这一句就注意看边p是否还有流量嘛,有的话就能进去。
			{
				h[y]=h[x]+1;
				q[ed]=y;
				ed++;
			}
		}
		st++;
	}
	return h[t];//其实这一句我本来不是这样做的不过看起来会简洁很多啦
}

然后就是dfs嘛

int dfs(int x,int f)//f指的是到这个点的流量
{
	if(x==ED) return f; //这句要记一下不然到时候忘记返回值就完蛋了
	int flow=0;
	for(int i=last[x];i!=-1;i=p[i].next	)
	{
		int y=p[i].y;
		if(h[y]==h[x]+1&&p[i].c>0&&f>flow)//如果能走,且还有流量,还有多的水。
		{
			int t=dfs(y,min(p[i].c,f-flow));//这句也挺重要,就能走的和拥有的取最小嘛
			flow+=t;//用了的水流要加上
			p[i].c-=t;//要让流量减去他们!
			p[p[i].other].c+=t;
		}
	}
	if(flow==0) return h[x]=0; //一句非常不错的优化吧,就是使它彻底断流再也没法再次在这次dfs中流了,防着它被两个及以上的点连然后一直被走做无用功吧。
	return flow;
}

比较重要的思想其实就是拆点,把一个点拆成入点和出点,然后根据题目乱搞一些呗,不过说真的我真的是被那些优化搞疯了。

然后就是呃最大流?,这玩意直接拿那个就是,spfa处理一下就好啦,就是最小费用最大流啊第一任务是最大流,第二任务是最小费用嘛,然后就用spfa处理bfs部分嘛也不难吧大概。
板子在此!P3381 【模板】最小费用最大流(https://www.luogu.com.cn/problem/P3381):

#include<bits/stdc++.h>
using namespace std;
int n,m;
int ST,ED;
int cost=0,maxx=0;
struct pp
{
	int x,y,c,d,next,other;
};
pp p[101010];
int dis[10101];
int q[101010];
int last[100010];
bool v[100110];
int len=0;
void ins(int x,int y,int c,int k)
{
	len++;
	int now1=len;
	len++;
	int now2=len;
	p[now1]={x,y,c,k,last[x],now2};
	last[x]=now1;
	p[now2]={y,x,0,-k,last[y],now1};
	last[y]=now2;
	return ;
}
bool spfa(int s,int t)
{
	memset(dis,63,sizeof(dis));
	int st=1,ed=2;
	int pd=dis[0];
	v[s]=false;
	q[st]=s;
	dis[s]=0;
	while(st!=ed)
	{
		int x=q[st];
		for(int i=last[x];i!=-1;i=p[i].next)
		{//rintf("*"); 
			int y=p[i].y;
			if(p[i].c>0&&dis[y]>dis[x]+p[i].d)//主要就是这里会用到spfa的思想不过可能会导致时间变慢不过问题不大。
			{
				dis[y]=dis[x]+p[i].d;
				if(v[y])
				{
					v[y]=false;
					q[ed]=y;
					ed++;
				}
			}
		}	
		v[x]=true;	
		st++;
	}

	if(dis[t]==pd) return false;
	else return true;
}
int dfs(int x,int f)
{
	if(x==ED) return f;
	int flow=0;
	v[x]=false;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(v[y]&&dis[y]==dis[x]+p[i].d&&flow<f)
		{
			int t=dfs(y,min(p[i].c,f-flow));
			cost+=t*p[i].d;//记得统计一下那些值
			flow+=t;
			p[i].c-=t;
			p[p[i].other].c+=t;
		}
	}
	v[x]=true;
	return flow;
}
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d%d%d%d",&n,&m,&ST,&ED);
	for(int i=1;i<=m;i++)
	{
		int x,y,c,d;
		scanf("%d%d%d%d",&x,&y,&c,&d);
		ins(x,y,c,d);
	}
	
	memset(v,true,sizeof(v));
	while(spfa(ST,ED))
	{
		maxx+=dfs(ST,1e9); 
	}
	printf("%d %d",maxx,cost);
//	printf("%d",cost);
	return 0;
}

二分图的话呢不想多说直接(https://www.luogu.com.cn/problem/P3386)板子:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
bool v[10001];
struct node
{
    int x,y,next;   
};
node e[110100];
int len=0;
int ans=0;
int f[100100];
int last[101000];
void ins(int x,int y)
{
    len++;
    e[len]={x,y,last[x]};
    last[x]=len;
}
bool dfs(int x)
{
    for(int i=last[x];i!=-1;i=e[i].next)
    {
        int y=e[i].y;
        if(!v[y])
        {
            v[y]=true;
            if(f[y]==0||dfs(f[y]))
            {
                f[y]=x;
                return true;
            }
        }
    }
    return false; 
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    memset(last,-1,sizeof(last));
    for(int i=1;i<=k;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);
    }
    memset(f,0,sizeof(f));  
    for(int i=1;i<=n;i++)
    {
        memset(v,false,sizeof(v));
        if(dfs(i)) ans++;
    }
    printf("%d",ans);
    return 0;
}

看看就好了,简单,还行,思想就是往前推,借用别人的话来说就是:
1.如果 后来的 和 以前的 发生矛盾,则 以前的 优先退让。
1.如果 以前的 退让之后没有对象可处,则 以前的 拒绝退让, 后来的 去寻找下一个匹配。
1.如果 后来的 谁也匹配不上了,那就这么单着吧。
如果想要更进一步的话可以学一学luogu上的网络流24题,有用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值