正在施工中。。。目测完不成了23333
前言
网络硫算法也是在省选等比赛中经常出现的一种算法,其模型。。。数量真的是太多了,数不过来了都。。。
而且网络流解决问题。。。都很巧妙。。。直接的模板题很少啦。。。主要是模型,构建网络流的模型是非常重要的。当然了,不会写也是不行的。。。
撒,让我们看一下网络流吧。。。
网络流算法
Dinic求最大流
简介
Dinic求法与朴素求法有一定的区别。
思想是:每次先使用BFS对整个网络进行分层,已经没有可行流的边将不会被访问到。如果在BFS过程中无法访问到终点,则视为算法结束。随后在DFS过程中,以刚才的分层为深度进行,一次DFS将会同时填充多条道路的流。然后重复这个过程,直到BFS无法访问到终点为止。
总结流程如下:
1.BFS对网络进行分层。如果无法访问到终点,则算法结束。
2.使用DFS进行可行流的填充。
3.重复步骤1。
另外,该算法也可以使用当前弧优化。
思想是:对于每个点,都连接了数条边,而有的边在某次可行流的填充中已经填满了,而这些边在本次DFS中,将不会再次发生改变,所以可以直接使用一个数组来代替邻接表中的head数组,略过已满流的边以达到节约时间的目的。
但是注意,每次重新分层后,都需要重置当前弧。
所以过程变成了这样:
1.初始化当前弧数组。
2.BFS对网络进行分层。如果无法访问到终点,则算法结束。
3.使用DFS进行可行流的填充。
4.重复步骤1。
没错就是这样啊哈哈哈哈。。。
编程实现
推荐去洛谷做一下P3376 【模板】网络最大流
标程就是标准的弧优化Dinic网络流。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
class MF
{
static const int MAXN=10005;
static const int MAXE=1000005;
static const int INF=2147480000;
private:
struct node{int u,v,w,next;};
node edge[MAXE];
int s,t,n,head[MAXN],cnt,f,level[MAXN],cur[MAXN];
queue<int>q;
bool inq[MAXN];
bool bfs()
{
memset(level,0,sizeof(level));
memset(inq,0,sizeof(inq));
bool ok=false;
inq[s]=true;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
if(u==t) ok=true;
for(int i=head[u];i>0;i=edge[i].next)
{
int v=edge[i].v,w=edge[i].w;
if(w&&!inq[v])
{
level[v]=level[u]+1;
inq[v]=true;
q.push(v);
}
}
}
return ok;
}
int dfs(int u,int flow)
{
if(u==t)
{
f+=flow;
return flow;
}
int sum=0;
for(int &i=cur[u];i>0&&flow;i=edge[i].next)
{
int v=edge[i].v,w=edge[i].w;
if(level[v]!=level[u]+1||!w) continue;
int a=dfs(v,min(flow-sum,w));
edge[i].w-=a;
edge[i^1].w+=a;
sum+=a;
}
return sum;
}
public:
void init(int _s,int _t,int _n)
{
n=_n;s=_s;t=_t;
cnt=1;f=0;
memset(head,0,sizeof(head));
}
void credge(int u,int v,int w)
{
edge[++cnt]=(node){u,v,w,head[u]};
head[u]=cnt;
edge[++cnt]=(node){v,u,0,head[v]};
head[v]=cnt;
}
int solve()
{
while(bfs())
{
for(int i=1;i<=n;i++) cur[i]=head[i];
dfs(s,INF);
}
return f;
}
};
MF F;
int n,m,s,t;
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m>>s>>t;
F.init(s,t,n);
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
F.credge(u,v,w);
}
cout<<F.solve()<<endl;
return 0;
}
SPFA求最大流最小费用
简介
使用SPFA来求最小费用最大流。。。
首先来讲,最大流最小费用是在保证最大流的情况下,使得花费的费用最小。
这里注意,每条边的费用是指单位流的费用。
具体思路是:每次使用SPFA来找出增广路径,然后增广。
关键在于使用SPFA,每次选择的路径是:一条从起点到终点的权值和最小的增广路径。
稍微多说一句,其他的最短路算法也是可行的,关键就在于最大流最小费用的特殊性质。因为增广过程中,每条边的流增加都是相同的,所以可以直接使用各边的权值作为增广的权值进行“预测”,然后找到权值和最小的路径增广。
总结一下:
1.使用最短路算法寻找增广路。
2.进行增广。
3.重复步骤1,直到无法到达终点。
编程实现
推荐去洛谷做一下P3381 【模板】最小费用最大流
但是请注意,由于洛谷近期加强了数据,所以使用SPFA的方法是无法拿到满分的。。。常数小一点的话,可以拿到80分,常数大点就只有70分了。。。
要想通过。。。应该需要将SPFA修改成堆优化的迪杰斯特拉吧。。。拒绝这样麻烦的修改。。。
以下程序的得分是80分。(明明原先都能过的啊啊啊)
得分情况:AAAAAAATTA
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
class MFMC
{
static const int MAXN=10005;
static const int MAXE=1000005;
static const int INF=2147480000;
private:
struct node{int u,v,w,c,next;};
node edge[MAXE];
int head[MAXN],cnt,s,t,n;
queue<int>q;
int spfa[MAXN];
bool inq[MAXN];
int path[MAXN];
bool SPFA()
{
for(int i=1;i<=n;i++)
{
spfa[i]=INF;
inq[i]=false;
}
inq[s]=true;
spfa[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i>0;i=edge[i].next)
{
int v=edge[i].v,c=edge[i].c,w=edge[i].w;
if(w&&spfa[v]>spfa[u]+c)
{
spfa[v]=spfa[u]+c;
path[v]=i;
if(!inq[v])
{
inq[v]=true;
q.push(v);
}
}
}
inq[u]=false;
}
if(spfa[t]==INF) return false;
return true;
}
int dfs(int v,int nowf,int &flow,int &cost)
{
if(v==s)
{
flow+=nowf;
return nowf;
}
int te=path[v];
nowf=dfs(edge[te].u,min(nowf,edge[te].w),flow,cost);
cost+=edge[te].c*nowf;
edge[te].w-=nowf;
edge[te^1].w+=nowf;
return nowf;
}
public:
void init(int _s,int _t,int _n)
{
n=_n;s=_s;t=_t;
cnt=1;
memset(head,0,sizeof(head));
}
void credge(int u,int v,int w,int c)
{
edge[++cnt]=(node){u,v,w,c,head[u]};
head[u]=cnt;
edge[++cnt]=(node){v,u,0,-c,head[v]};
head[v]=cnt;
}
void solve(int &flow,int &cost)
{
flow=0;
cost=0;
while(SPFA())
{
int nzdl=INF;
dfs(t,nzdl,flow,cost);
}
}
};
MFMC F;
int n,m,s,t;
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m>>s>>t;
F.init(s,t,n);
for(int i=1;i<=m;i++)
{
int u,v,w,c;
cin>>u>>v>>w>>c;
F.credge(u,v,w,c);
}
int flow=0,cost=0;
F.solve(flow,cost);
cout<<flow<<" "<<cost;
return 0;
}
常见网络流模型
对偶图
看这道题:
[BeiJing2006]狼抓兔子
现在小朋友们最喜欢的”喜羊羊与灰太狼”,话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:
左上角点为(1,1),右下角点为(N,M)(上图中N=4,M=5).有以下三种类型的道路
1:(x,y)<==>(x+1,y)
2:(x,y)<==>(x,y+1)
3:(x,y)<==>(x+1,y+1)
道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的. 左上角和右下角为兔子的两个窝,开始时所有的兔子都聚集在左上角(1,1)的窝里,现在它们要跑到右下解(N,M)的窝中去,狼王开始伏击这些兔子.当然为了保险起见,如果一条道路上最多通过的兔子数为K,狼王需要安排同样数量的K只狼,才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的狼的数量要最小。因为狼还要去找喜羊羊麻烦.
这个题使用对偶图优化。
什么?对偶图是什么?
对偶图,就是指一个图的每条边,必定会有两个面在其左右侧。
比如说:
每个边都有两个面相邻(包含最外面的大的面)。
我们知道,网络流的最大流即是网络的最小割。
割:在图上画一条线阻断S→T的路径,被阻断的路径的权值和就是割。
最小割:一个图的割中最小的那个。
那么这个图由于具有对偶图的性质,那么我们可以这样转化成求最小割:
如图所示,将最外侧的面分割成两个面。
然后,将每个面都作为一个点,然后将每条边的两侧的两个面用一条无向边连接起来(除了刚才分割开的最外侧的面),如图。
然后,跑一遍最短路算法即可,起点和终点就是刚才分割的两个面。
其实主要有难度的就是将对偶图转化为最小割的图,很容易混淆,涉及到一部分数学计算。
标准程序如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
//#define DEBUG
const int MAXN=2000005;
const int MAXE=2000005<<2;
const int INF=2147480000;
class SPFA
{
private:
struct node{int u,v,w,next;};
node edge[MAXE];
int cnt,head[MAXN],spfa[MAXN];
bool inq[MAXN];
queue<int>q;
public:
void credge(int u,int v,int w)
{
#ifdef DEBUG
cout<<"[EDGE] "<<u<<" <--"<<w<<"--> "<<v<<endl;
#endif
edge[++cnt]=(node){u,v,w,head[u]};
head[u]=cnt;
edge[++cnt]=(node){v,u,w,head[v]};
head[v]=cnt;
}
int solve(int s,int t)
{
inq[s]=true;
q.push(s);
memset(spfa,0x3f,sizeof(spfa));
spfa[s]=0;
while(!q.empty())
{
int u=q.front();q.pop();
inq[u]=false;
for(int i=head[u];i>0;i=edge[i].next)
{
int v=edge[i].v,w=edge[i].w;
if(spfa[v]>spfa[u]+w)
{
spfa[v]=spfa[u]+w;
if(!inq[v])
{
q.push(v);
inq[v]=true;
}
}
}
}
return spfa[t];
}
};
SPFA F;
int n,m,s,t,line;
bool SSSPJ1()//Special situation 1
{
if(n==1||m==1)
{
int ans=INF,temp,ending=max(m,n)-1;
while(--ending)
{
//readint(temp);
cin>>temp;
ans=min(ans,temp);
}
cout<<ans;
return true;
}
return false;
}
void DRP_1()//Data reading precedure 1
{
int temp;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=line;j++)
{
cin>>temp;
if(i==1) F.credge(s,j,temp);
else if(i==n) F.credge(2*(i-2)*line+line+j,t,temp);
else F.credge(2*(i-2)*line+line+j,2*(i-2)*line+line+j+line,temp);
}
}
}
void DRP_2()//Data reading precedure 2
{
int temp;
for(int i=1;i<n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>temp;
if(j==1) F.credge(line+(i-1)*line*2+j,t,temp);
else if(j==m) F.credge((i-1)*2*line+j-1,s,temp);
else F.credge((i-1)*2*line+j-1,(i-1)*2*line+j-1+m,temp);
}
}
}
void DRP_3()//Data reading precedure 3
{
int temp;
for(int i=1;i<n;i++)
{
for(int j=1;j<m;j++)
{
cin>>temp;
F.credge((i-1)*line*2+j,(i-1)*line*2+j+line,temp);
}
}
}
int main()
{
ios::sync_with_stdio(false);
//readint(n);readint(m);
cin>>n>>m;
if(SSSPJ1()) return 0;
line=m-1;
s=1999999;t=s+1;
DRP_1();
DRP_2();
DRP_3();
cout<<F.solve(s,t);
return 0;
}
标程建图方法:
边的3种颜色代表了3个DRP的功能。这样分类相对来讲比较有条理(上下、左右、斜)
最大权闭合子图
最大权闭合子图致力于解决这样一个问题:
对于一个图,点有权值,有正权点,有负权点,问从起点走,走那些路能够使获得的点的权值和最大(没有固定终点,也可以不走)。
我们用网络流来解决这类问题,并统称他们为:最大权闭合子图问题。
解决方案如下:
1.将图读入,存储。
2.将图中从原点能够一次性到达的正权点向起点连接边,所有负权点向终点连接边。起点/终点向每一个点连接的边权值为点权的绝对值。
3.图中正常的边依旧按照原有关系进行连接,但是边权值全部为正无穷。
4.执行最大流算法。
5.将所有正权点的权值相加,减去最大流,即是需要的结果。
为什么呢?道洗呆?
======================施工(弃坑)现场======================
求最小点割集
二分图模型
【广告时间】我同学的博客也是棒棒哒,有兴趣来看看吧!