从浅到深,尽量每种题型都选出至少一道例题。代码在这-> 点击打开链接
1.最大流问题,s到t最大流为t到s最小流,最小割即最大流,求最小割割边也可以在网络流中实现,附一带求割边代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define INF 0x7fffffff
#define maxn 25000
using namespace std;
struct Edge{
int from,to,cap,flow;
};
class Dinic{
private:
int s,t,c;
vector<Edge>edges;
vector<int>G[maxn];//结点
bool vis[maxn];
int dist[maxn];
int cur[maxn];
public:
int n,m;
void AddEdge(int from,int to,int cap){
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});
c=edges.size();
G[from].push_back(c-2);
G[to].push_back(c-1);
}
bool BFS(){
queue<int>Q;
memset(vis,0,sizeof(vis));
Q.push(s);
dist[s]=0;
vis[s]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(!vis[e.to]&&e.cap>e.flow){
vis[e.to]=1;
dist[e.to]=dist[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x,int a){
if(x==t||a==0)return a;
int flow=0,f;
for(int& i=cur[x];i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(dist[x]+1==dist[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0){
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)break;
}
}
return flow;
}
int Maxflow(int s,int t){
this->s=s;this->t=t;
int flow=0;
while(BFS()){
memset(cur,0,sizeof(cur));
flow+=DFS(s,INF);
flow+=DFS(s,INF);
}
return flow;
}
void init(){
edges.clear();
for(int i=0;i<maxn;i++){
G[i].clear();
dist[i]=0;
}
}
vector<int> Mincut(){
BFS();
vector<int> ans;
for(int i=0;i<edges.size();i++){
Edge& e=edges[i];
if(vis[e.from]&&!vis[e.to]&&e.cap>0)ans.push_back(i);
}
return ans;
}
}Do;
简单来说就是从源点s到源点t求一遍最大流。但是大部分题是不会给你源点的,所以一般如果发现这是最大流的话即可自建源点和网络流中的其他节点链接然后跑一遍网络流即可。
eg1:hdu 1532
题意:农夫就修建排水系统,给每个排水管道设置了最大流量;首先输入两个数n , m ; n为排水管道的数量,m为节点的数量,接下来就是n行数,每一行分为x1,x2,x3;x1 , x2为节点的序号,x3为流量;然后问从1号节点到m号节点的最大流是多少?
思路:裸模板题,源点都不需自建。
Eg2:hdu 3572
题意:有M个机器,有N个任务。每个任务必须在Si或者以后开始做,在Ei或者之前完成,完成任务必须处理Pi个时间单位。其中,每个任务可以在任意(空闲)机器上工作,每个机器的同一时刻只能工作一个任务,每个任务在同一时刻只能被一个机器工作,而且任务做到一半可以打断,拿去其他机器做。问:能否在规定时间内把任务做完。
思路:这一题需要建立源点汇点,我们可以选择0为源点,然后源点与每个任务都连一条边,容量为每个任务要求的天数pi,然后每个任务都与相应的时间点(si到ei之间)连边,边容量为1,最后我们要确定汇点,汇点可以取t=max+n+1(其中max为结束时间的最大值,n为任务数),这样确定好汇点之后,再在每个时间点与汇点之间连边,边容量为m,为机器数,最后判断流到汇点的流量等于sum | pi 即可。
Eg3:hdu 3081 也可以当做二分图匹配问题
理论上二分图匹配问题都可以用网络流来解决,只是时间和空间的效率问题有差异而已。
题意:n个男孩n个女孩,女孩选男孩,每个女孩都要选到不同的人。如果女孩a和男孩不矛盾则可以在一起,a也可以选择和女孩c不矛盾的d男孩在一起,输入里会告诉你这些情况。女孩每轮都选择之前没选过的男孩,每个女孩都要有boyfriend,问总共能选几轮。
思路:第一眼一般会选择匈牙利算法,因为编码难度小太多,但是最大流其实也可以。
二分图匹配:并查集记录可以在一起的男女,对原图跑一遍匈牙利,然后把选中的边去掉,继续跑匈牙利,知道无法完全匹配,跑了多少遍就选了k轮。
最大流:并查集记录可以在一起的男女,建立源点s和汇点t,女孩和所有能连的男孩连起来容量为1的边,然后二分可能进行的轮次k(n>=k>=0),每一次建立s到所有女孩容量为k的边,所有男孩到t容量为k的边,如果满流则更新ans。其正确性很容易体现,满流的话则每一个男孩都有k个女孩选中了,那就是说有k个轮次可以选择了,即可以视作每次去掉n个关系可去k次,而且每次都不同。
2.费用流问题,最小(大)费用最大流(建议不懂得时候画画图,很有用)
费用流问题即在以上的基础上添加了费用的概念,百度百科的解释是在一个网络中每段路径都有“容量”和“费用”两个限制的条件下,此类问题的研究试图寻找出:流量从A到B,如何选择路径、分配经过路径的流量,可以在流量最大的前提下,达到所用的费用最小的要求。我们知道最大流的方案是不唯一的,又知道每条边上的单位费用,在此条件下让费用最小或者最大。
最大费用只需建图时费用取反,跑最小费用流,结果取反即可。
接下来是例子:
Eg1:hdu 1533 Going Home
其实这又是一道二分图匹配的问题,只不过是最大匹配,可以用KM解决。这里给出费用流的方法。
题意:N行M列的矩阵,其中“.”代表空地,“H”代表房子,“m”代表人,其中有n个房子和n个人。现在要求每个人进入一间房子,且人走一步需要支付1美元,要求每个人只能进入一间房子,每个房子只能容纳一个人。问最小需要花费多少美元才能让所有人都进入到房子中。
思路:基础题,建立两个超级源点s和t,每个h和m之间建立一条容量为1花费为路径长度的边,s和每个h建立容量为1花费为0的边,同理每个m和t建立容量为1花费为0的边,s到t跑一边最小费用流即花费的最小费用。
Eg2:hdu 3667 Transportation拆边费用流(很重要)
思路:这一题和别的费用流的区别就是费用并不是线性增加而是ai*x^2,ai是边i上的系数,x是边i的容量。这样原本模板中的按线性增加容量的办法就行不通。我们可以从多重背包时的分解容量得到启发,由于题目中的c(容量)<=5,所以我们可以将边拆成费用为a,3a,5a,7a....的边,这样在计算费用的时候可以得到a,4a,9a.....的平方级增长。
Eg3:hdu 2255奔小康赚大钱(多重匹配)
题意是中文就不解释了。
思路:其实这一题用费用流是不能过的会超时,正确的解法其实是KM二分匹配,但是这并影响我们的思考,而且也是最大费用流,不妨用费用流验证下取反是否正确?
Eg4:hdu 5644King's Pilots(bestcoder #75 1005)
这题和之前的机器做任务有一点点相似。但是将每一天拆成两个点的想法很值得思考,贴一篇自己之前写过的题解http://blog.csdn.net/carryheart/article/details/50890470。
Eg5:zoj 3933
这一题坑点很多,但是最值得注意的地方是需要输出选中的边,这些边就是跑完费用流之后边流量即flow不为0的边。另外,这题需要选择较为快捷的费用流模板。。不然会超时。。。这就是当初比赛时我没过的原因。。
其实网络流的题目只要想到了建图,真是已经完成了80%了。
3.有上下界的网络流
以下转自http://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html
代码博主自己编写。
此类问题可以分为三小类问题:
一、无源汇有上下界最大流--即没有对结点的流入有除这些点之外的点(一般需要自己定义)的限制,如eg2中对每天(每一天都是一个结点)的拍摄的张数的限制数d即产生了一个源点。而eg1中则没有这样的点。
二、有源汇有上下界最大流
三、有源汇有上下界最小流
1、无源汇有上下界最大流
题目链接: sgu194 Reactor Cooling
题目大意:给n个点,及m根pipe,每根pipe用来流躺液体的,单向的,每时每刻每根pipe流进来的物质要等于流出去的物质,要使得m条pipe组成一个循环体,里面流躺物质。并且满足每根pipe一定的流量限制,范围为[Li,Ri].即要满足每时刻流进来的不能超过Ri(最大流问题),同时最小不能低于Li。
解题思路:O(-1)。
建图模型: 以前写的最大流默认的下界为0,而这里的下界却不为0,所以我们要进行再构造让每条边的下界为0,这样做是为了方便处理。对于每根管子有一个上界容量up和一个下界容量low,我们让这根管子的容量下界变为0,上界为up-low。可是这样做了的话流量就不守恒了,为了再次满足流量守恒,即每个节点"入流=出流”,我们增设一个超级源点st和一个超级终点sd。我们开设一个数组du[]来记录每个节点的流量情况。
du[i]=in[i](i节点所有入流下界之和)-out[i](i节点所有出流下界之和)。
当du[i]大于0的时候,st到i连一条流量为du[i]的边。
当du[i]小于0的时候,i到sd连一条流量为-du[i]的边。
最后对(st,sd)求一次最大流即可,当所有附加边全部满流时(即maxflow==所有du[]>0之和),有可行解。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define INF 0x7fffffff
#define maxn 210
using namespace std;
int dn[maxn*maxn*2];
int du[maxn];
struct Edge{
int from,to,cap,flow;
};
class Dinic{
public:
int s,t,c;
vector<Edge>edges;
vector<int>G[maxn];//结点
bool vis[maxn];
int dist[maxn];
int cur[maxn];
int n,m;
void AddEdge(int from,int to,int cap){
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});//反向边,无向图容量和正向边相同,有向图为0
c=edges.size();
G[from].push_back(c-2);
G[to].push_back(c-1);
}
bool BFS(){
queue<int>Q;
memset(vis,0,sizeof(vis));
Q.push(s);
dist[s]=0;
vis[s]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(!vis[e.to]&&e.cap>e.flow){
vis[e.to]=1;
dist[e.to]=dist[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x,int a){
if(x==t||a==0)return a;
int flow=0,f;
for(int& i=cur[x];i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(dist[x]+1==dist[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0){
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)break;
}
}
return flow;
}
int Maxflow(int s,int t){
this->s=s;this->t=t;
int flow=0;
while(BFS()){
memset(cur,0,sizeof(cur));
flow+=DFS(s,INF);
flow+=DFS(s,INF);
}
return flow;
}
void init(){
edges.clear();
for(int i=0;i<maxn;i++){
G[i].clear();
dist[i]=0;
}
}
}Do;
int main()
{
int st,sd;
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
Do.init();
memset(du,0,sizeof(du));
for(int i=0;i<m;i++)
{
int u,v,l,h;
scanf("%d%d%d%d",&u,&v,&l,&h);
Do.AddEdge(u,v,h-l);
du[u]-=l;
du[v]+=l;
dn[i]=l;
}
int sumdu=0;
st=0;
sd=n+1;
for(int i=1;i<=n;i++)
{
if(du[i]>0)
{
sumdu+=du[i];
Do.AddEdge(st,i,du[i]);
}
else if(du[i]<0)
{
Do.AddEdge(i,sd,-du[i]);
}
}
int f=Do.Maxflow(st,sd);
if(f==sumdu)
{
cout<<"YES"<<endl;
for(int i=0;i<m;i++)
{
cout<<Do.edges[i*2].flow+dn[i]<<endl;
}
}
else
{
cout<<"NO"<<endl;
}
}
}
2、有源汇有上下界的最大流
题目大意:一个屌丝给m个女神拍照,计划拍照n天,每一天屌丝最多个C个女神拍照,每天拍照数不能超过D张,而且给每个女神i拍照有数量限制[Li,Ri],对于每个女神n天的拍照总和不能超过Gi,如果有解求屌丝最多能拍多少张照,并求每天给对应女神拍多少张照;否则输出-1。
解题思路:增设一源点st,汇点sd,st到第i天连一条上界为Di下界为0的边,每个女神到汇点连一条下界为Gi上界为oo的边,对于每一天,当天到第i个女孩连一条[Li,Ri]的边。
建图模型:源点s,终点d。超级源点ss,超级终点dd。首先判断是否存在满足所有边上下界的可行流,方法可以转化成无源汇有上下界的可行流问题。怎么转换呢?
增设一条从d到s没有下界容量为无穷的边,那么原图就变成了一个无源汇的循环流图。接下来的事情一样,超级源点ss连i(du[i]>0),i连超级汇点(du[i]<0),
对(ss,dd)进行一次最大流,当maxflow等于所有(du[]>0)之和时,有可行流,否则没有。
当有可行流时,删除超级源点ss和超级终点dd,再对(s,d)进行一次最大流,此时得到的maxflow则为题目的解。为什么呢?因为第一次maxflow()只是求得所有满足下界的流量,而残留网络(s,d)路上还有许多自由流(没有和超级源点和超级汇点连接的边)没有流满,所有最终得到的maxflow=(第一次流满下界的流+第二次能流通的自由流)。
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<set>
#include<map>
#include<vector>
#include<cstring>
#include<stack>
#include<cmath>
#include<queue>
using namespace std;
#define CL(x,v); memset(x,v,sizeof(x));
#define INF 0x3f3f3f3f
#define LL long long
#define REP(i,r,n) for(int i=r;i<=n;i++)
#define RREP(i,n,r) for(int i=n;i>=r;i--)
const int MAXN=1500;
struct Edge{
int from,to,cap,flow;
};
bool cmp(const Edge& a,const Edge& b){
return a.from < b.from || (a.from == b.from && a.to < b.to);
}
struct Dinic{
int n,m,s,t;
vector<Edge> edges;
vector<int> G[MAXN];
bool vis[MAXN];
int d[MAXN];
int cur[MAXN];
void init(int n){
this->n=n;
for(int i=0;i<=n;i++)G[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int cap){
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});//当是无向图时,反向边容量也是cap,有向边时,反向边容量是0
m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BFS(){
CL(vis,0);
queue<int> Q;
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(!vis[e.to]&&e.cap>e.flow){
vis[e.to]=1;
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x,int a){
if(x==t||a==0)return a;
int flow=0,f;
for(int& i=cur[x];i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0){
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)break;
}
}
return flow;
}
//当所求流量大于need时就退出,降低时间
int Maxflow(int s,int t,int need){
this->s=s;this->t=t;
int flow=0;
while(BFS()){
CL(cur,0);
flow+=DFS(s,INF);
if(flow>need)return flow;
}
return flow;
}
//最小割割边
vector<int> Mincut(){
BFS();
vector<int> ans;
for(int i=0;i<edges.size();i++){
Edge& e=edges[i];
if(vis[e.from]&&!vis[e.to]&&e.cap>0)ans.push_back(i);
}
return ans;
}
void Reduce(){
for(int i = 0; i < edges.size(); i++) edges[i].cap -= edges[i].flow;
}
void ClearFlow(){
for(int i = 0; i < edges.size(); i++) edges[i].flow = 0;
}
};
int n,m;
Dinic solver;
int du[MAXN];
int dn[MAXN][MAXN];
int id[MAXN][MAXN];
int main()
{
while(~scanf("%d%d",&n,&m))
{
int s=0,t=n+m+1;
int ss=n+m+2,tt=n+m+3;
int a,b,c;
CL(du,0);
CL(dn,0);
CL(id,0);
solver.init(n+m+5);
REP(i,1,m)
{
scanf("%d",&a);
solver.AddEdge(n+i,t,INF-a);
du[n+i]-=a;
du[t]+=a;
}
int C,D;
REP(i,1,n)
{
scanf("%d%d",&C,&D);
solver.AddEdge(s,i,D);
REP(j,1,C)
{
scanf("%d%d%d",&a,&b,&c);
solver.AddEdge(i,a+n+1,c-b);
du[i]-=b;
du[a+n+1]+=b;
dn[i][a]=b;
id[i][a]=solver.edges.size()-2;
}
}
solver.AddEdge(t,s,INF);
int sum=0;
REP(i,1,t)
{
if(du[i]<0)
{
solver.AddEdge(i,tt,-du[i]);
}
else if(du[i]>0)
{
solver.AddEdge(ss,i,du[i]);
sum+=du[i];
}
}
int maxflow=solver.Maxflow(ss,tt,INF);
if(maxflow==sum)
{
int ans=solver.Maxflow(s,t,INF);
printf("%d\n",ans);
for(int i=1;i<=n;i++)
{
for(int j=0;j<m;j++)
if(id[i][j])
printf("%d\n",solver.edges[id[i][j]].flow+dn[i][j]);
}
}
else printf("-1\n");
printf("\n");
}
return 0;
}
3、有源汇有上下界的最小流
题目链接: sgu176 Flow construction
题目大意:有一个类似于工业加工生产的机器,起点为1终点为n,中间生产环节有货物加工数量限制,输出u v z c, 当c等于1时表示这个加工的环节必须对纽带上的货物全部加工(即上下界都为z),c等于0表示加工没有上界限制,下界为0,求节点1(起点)最少需要投放多少货物才能传送带正常工作。
解题思路:
1、du[i]表示i节点的入流之和与出流之和的差。
2、增设超级源点st和超级汇点sd,连(st,du[i](为正)),(-du[i](为负),sd)。 ///增设超级源点和超级汇点,因为网络中规定不能有弧指向st,也不能有流量流出sd
3、做一次maxflow()。
4、源点(Sd)和起点(St)连一条容量为oo的边。
5、再做一次maxflow()。
6、当且仅当所有附加弧满载时有可行流,最后答案为flow[(Sd->St)^1],St到Sd最大流就是Sd到St最小流。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
const int mn=111;
const int mm=11111;
const int oo=0x3fffffff;
int node, st, sd, edge, St, Sd, Edge;
int reach[mm], flow[mm], next[mm];
int head[mn], work[mn], dis[mn], que[mn];
int du[mm], ans[mm], id[mm], dn[mm];
inline void init(int _node, int _st, int _sd)
{
node=_node, st=_st, sd=_sd;
for(int i=0; i<node; i++)
head[i]=-1, du[i]=0;
edge=0;
}
inline void addedge(int u, int v, int c1, int c2, int ID)
{
id[edge]=ID, reach[edge]=v, flow[edge]=c1, next[edge]=head[u],head[u]=edge++;
id[edge]=0, reach[edge]=u, flow[edge]=c2, next[edge]=head[v],head[v]=edge++;
}
bool bfs()
{
int u, v, l=0, h=0;
for(int i=0; i<node; i++) dis[i]=-1;
que[l++]=st;
dis[st]=0;
while(l!=h)
{
u=que[h++];
if(h==mn) h=0;
for(int i=head[u]; i>=0; i=next[i])
{
v=reach[i];
if(flow[i]&&dis[v]<0)
{
dis[v]=dis[u]+1;
que[l++]=v;
if(l==mn) l=0;
if(v==sd) return true;
}
}
}
return false;
}
int dfs(int u, int exp)
{
if(u==sd) return exp;
for(int &i=work[u]; i>=0; i=next[i])
{
int v=reach[i], tp;
if(flow[i]&&dis[v]==dis[u]+1&&(tp=dfs(v,min(flow[i],exp)))>0)
{
flow[i]-=tp;
flow[i^1]+=tp;
return tp;
}
}
return 0;
}
void Dinic()
{
int max_flow=0, flow;
while(bfs())
{
for(int i=0; i<node; i++) work[i]=head[i];
while(flow=dfs(st,oo)) max_flow+=flow;
}
}
int main()
{
int n, m;
while(~scanf("%d%d",&n,&m))
{
init(n+1,1,n);
for(int i=1; i<=m; i++)
{
int u, v, c, k;
scanf("%d%d%d%d",&u,&v,&c,&k);
if(k) du[u]-=c, du[v]+=c, ans[i]=c;
else addedge(u,v,c,0,i);
}
St=st, Sd=sd, Edge=edge;
st=node, sd=node+1, node+=2; ///增设超级源点和超级汇点,因为网络中规定不能有弧指向st,也不能有流量流出sd
head[st]=head[sd]=-1;
for(int i=1; i<=n; i++)
{
if(du[i]>0) addedge(st,i,du[i],0,0);
if(du[i]<0) addedge(i,sd,-du[i],0,0);
}
Dinic();
addedge(Sd,St,oo,0,0);
Dinic();
bool flag=true;
for(int i=head[st]; i>=0; i=next[i])
if(flow[i]>0) ///当且仅当附加弧达到满负载有可行流
{
flag=false;
break;
}
if(!flag)
puts("Impossible");
else
{
int res=0, i;
for(i=head[Sd]; i>=0; i=next[i])
if(reach[i]==St) break;
res=flow[i^1];
printf("%d\n",res);
for(i=0; i<Edge; i++) ans[id[i]]=flow[i^1];
for(i=1; i<=m; i++)
{
if(i!=m) printf("%d ",ans[i]);
else printf("%d\n",ans[i]);
}
}
}
return 0;
}