网络流-最大流

网络流

在有向图G(V,E)中
有一源点S,入度为0
有一汇点T,出度为0
任一弧(u,v)有一非负流量c(u,v)

最大流

可行流:在容量网络G中满足以下条件的网络流f,称为可行流.
0<=f(u,v)<=c(u,v)(弧流量记为f(u,v),弧容量记为c(u,v))
流入一个点的流量等于流出这个点的流量(除源点和汇点)

最大流:在容量网络中,具有最大流量的可行流,称为最大流

Edmond—Karp算法

从源点S开始,寻找增广路,找到已找到的增广路中的最小残量delta。
将增广路上的每一流量减去delta,最大流值加上delta。
听起来是个挺暴力的算法。
但是还是有一个小技巧,就是利用对于反向边的增加delta来让程序可以对以上操作“反悔”,又让时间复杂度远远小于回溯。
代码就不放了。

Dinic算法

也是一种增广路算法,是利用分层图来优化增广路的寻找过程。
从源点S开始做BFS,给每个点分配一个深度h。
每次寻找增广路之后,都做一遍BFS就行。
【还有个当前弧优化来着。。懒得写了】

#include <bits/stdc++.h>
using namespace std;

const int maxdelta=2e9;

struct hazaking
{
	int y,next,v;
}e[1000005];
int linkk[1000005];
int q[1000005];
int h[1000005];
int add;

inline int read()
{
	int sum=0,flag=1;
	char c=getchar();
	for (;c<'0' || c>'9';c=getchar())
	 if (c=='-') flag=-1;
	for (;c>='0' && c<='9';c=getchar())
	 sum=(sum<<3)+(sum<<1)+c-48;
	return sum*flag; 
}

int m,n; 
int t=1;
bool flag=true;
long long ans=0;

inline void init()
{
	m=read();
	n=read();
	for (int i=1;i<=m;i++)
	 {
	 	int x=read(),y=read(),z=read();
	 	e[++t].next=linkk[x];
	 	e[t].y=y;
	 	e[t].v=z;
	 	linkk[x]=t;
	 	e[++t].next=linkk[y];
	 	e[t].y=x;
	 	e[t].v=0;
	 	linkk[y]=t;
	 }
}

void bfs()
{
	memset(h,-1,sizeof(h));//深度数组初始化
	int qh=1,qt=1;
	q[1]=1;
	h[1]=0;
	while (qh<=qt)
	 {
	 	for (int i=linkk[q[qh]];i;i=e[i].next)
	 	 if (h[e[i].y]==-1 && e[i].v) //如果该点还未分配深度且有剩余残量
	 	  {
	 	   h[e[i].y]=h[q[qh]]+1;
	 	   q[++qt]=e[i].y;
	      }
	    qh++;
	 }
	if (h[n]!=-1) flag=true;//如果成功找到汇点并分配深度,则继续寻找增广路
}

int dfs(int k,int delta)
{
	if (k==n) return delta;
	int ma=0;
	for (int i=linkk[k];i && ma<delta;i=e[i].next) 
	 if (h[e[i].y]==h[k]+1 && e[i].v)//如果满足新点深度是当前点深度+1且有剩余残量
	  {
	  	if (add=dfs(e[i].y,min(e[i].v,delta-ma)))
	  	 {
	  	 	ma+=add;
	  	 	e[i].v-=add;
	  	 	e[i^1].v+=add;
	  	 }
	  }
	if (!ma) h[k]=-1;
    return ma;
}

inline void work()
{
    while (flag)
     {
     	flag=false;
     	int sum=0;
     	bfs();//分配深度
     	if (!flag) break;
     	while (sum=dfs(1,maxdelta))//找寻增广路
     	 ans+=sum;
     }
}

inline void outo()
{
    printf("%lld\n",ans);
}

int main()
{
	init();
	work();
	outo();
	return 0;
} 

奶牛的聚会

N(3<=N<=200)头奶牛要办一个新年晚会。每头牛都会烧几道菜。一共有D(5<=D<=100)道不同的菜肴。每道菜都可以用一个1到D之间的数来表示。 晚会的主办者希望能尽量多的菜肴被带到晚会,但是每道菜的数目又给出了限制。每头奶牛可以带K(1<=K<=5)道菜,但是必须是各不相同的(例如,一头牛不能带三块馅饼,但是可以带上一块馅饼,一份面包,和一些美味的桔子酱苜蓿)。那么,究竟有多少菜可以被带来晚会呢?

这是一道最大流的题目
设源点为1
1.可以先由源点连出n条权值为k的弧,第i条弧的流量代表第i头奶牛带了多少种菜
2.再由1.中每条弧的终点连出z条权值为1的弧,第j条弧流量01表示是否带了第i种奶牛可以准备的第j种菜
3.再由2.中每条弧的终点连出权值为第i种菜的数目限制的弧,流量表示晚会上多少第i种菜,这里第i条弧起点可能是2.中多条弧的终点,也就是说,可以有多条奶牛将菜运送到第i条弧
注意要修改n的大小
所以就可以像下面这样构建网络了:


inline void start()
{
	for (int i=1;i<=d;i++)
	  insert(n+i+1,n+d+2,read());//每种菜
	for (int i=1;i<=n;i++)
	 {
	 	int z=read();
	 	insert(1,i+1,k);//奶牛带菜 
	 	for (int j=1;j<=z;j++)
	 	  insert(i+1,n+read()+1,1);//带哪些菜 
	 }
	n=n+d+2;
	return;
}

然后跑EK或者dinic都行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值