网络流
在有向图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来让程序可以对以上操作“反悔”,又让时间复杂度远远小于回溯。
【很多人都用BFS写,我一般使用DFS找增广路】
【一般来说将流量减去会直接将邻接表中储存的权值容量减去】
具体看代码:
#include <bits/stdc++.h>
using namespace std;
const int maxdelta=2e9;//进入DFS前,delta的值
struct hazaking
{
int y,next,v;
}e[1005];
int linkk[1005];//建立一个邻接表
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;//最大流值
int add;//用于保存delta值
bool vis[1005];
void dfs(int k,int delta)
{
vis[k]=true;
if (k==n)
{
ans+=delta;//将加到最大流值中
add=delta;//保存delta到标记add中
flag=true;
return;
}
for (int i=linkk[k];i;i=e[i].next)
if (!vis[e[i].y] && e[i].v)//如果没有被访问过且还有剩余残量
{
dfs(e[i].y,min(e[i].v,delta));//继续找增广路
if (flag)
{
e[i].v-=add;//减去delta
e[i^1].v+=add;//对于反向边来说加上delta
return;
}
}
}
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;
}
}
inline void work()
{
while (flag)
{
flag=false;
memset(vis,false,sizeof(vis));
dfs(1,maxdelta);
}
}
inline void outo()
{
printf("%lld\n",ans);
}
int main()
{
init();
work();
outo();
return 0;
}
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都行,或者还可以跑FF或者SAP或者ISAP或者PR都星