网络流中常用的几个定理:
1、最大流的增广路
2、最大流Dinic搭配用法
3、最大流=最小割(博主表示经常用)
备注:本博客资料来源XZY,SYC,百度
网络流中的常用模板
一、最大流(最小割)模板
重点是要把S,T设好,而且有些题目还要拆点,因此建模是靠经验刷出来的!
#define inf 1e9
int n,m,S,T;
struct hand{int to,next,w;}a[__];
int cnt=1,head[_],cur[_],dep[_];
void link(int u,int v,int w)
{
a[++cnt]=(hand){v,head[u],w};head[u]=cnt;
a[++cnt]=(hand){u,head[v],0};head[v]=cnt;
//很重要的“连反边”
}
queue<int>Q;
bool bfs()
{
memset(dep,0,sizeof(dep));
Q.push(S);dep[S]=1;
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=head[u];i;i=a[i].next)
{
int v=a[i].to;
if(!dep[v]&&a[i].w)
{
Q.push(v);
dep[v]=dep[u]+1;
}
}
}
return dep[T];
}
int dfs(int u,int flow)
{
if(u==T||!flow)return flow;
int ret=0;
for(int &i=cur[u];i;i=a[i].next)
{
int v=a[i].to;
if(dep[v]==dep[u]+1&&a[i].w)
{
int tmp=dfs(v,min(flow,a[i].w));
a[i].w-=tmp;
a[i^1].w+=tmp;
flow-=tmp;
ret+=tmp;
}
}
return ret;
}
void dinic()
{
int ans=0;
while(bfs())
{
memcpy(cur,head,sizeof(cur));
ans+=dfs(S,inf);
}
printf("%d\n",ans);
}
二、费用流(最小费用最大流)模板
int n,m,S,T;
struct hand{int to,next,w,cost;}a[__<<1];
int cnt=1,head[_];
void link(int u,int v,int w,int cost)
{
a[++cnt]=(hand){v,head[u],w,cost};
head[u]=cnt;
}
queue<int>Q;
int dis[_],belong[_],fa[_];
bool vis[_];
bool bfs()
{
memset(dis,63,sizeof(dis));
dis[S]=0;Q.push(S);
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=head[u];i;i=a[i].next)
{
int v=a[i].to;
if(a[i].w&&dis[v]>dis[u]+a[i].cost)
{
dis[v]=dis[u]+a[i].cost;
belong[v]=i;
fa[v]=u;
if(!vis[v]){vis[v]=1;Q.push(v);}
}
}
vis[u]=0;
}
return dis[T]<dis[0];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&S,&T);int x,y,w,cost;
while(m--)
{
scanf("%d%d%d%d",&x,&y,&w,&cost);
link(x,y,w,cost);
link(y,x,0,-cost);
}
int anscost=0,anssum=0;
while(bfs())
{
int flow=1e9;
for(int i=T;i!=S;i=fa[i])
{
int t=belong[i];
flow=min(flow,a[t].w);
}
anscost+=dis[T]*flow;
anssum+=flow;
for(int i=T;i!=S;i=fa[i])
{
int t=belong[i];
a[t].w-=flow;
a[t^1].w+=flow;
}
}
printf("%d %d\n",anssum,anscost);
return 0;
}
例题推荐与分析
一、试题库问题
<<问题描述:
假设一个试题库中有n道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。
<<编程任务:
对于给定的组卷要求,计算满足要求的组卷方案。
<<输入输出格式
输入格式:
第1行有2个正整数k和n (2 <=k<= 20, k<=n<= 1000)
k 表示题库中试题类型总数,n 表示题库中试题总数。第2 行有k 个正整数,第i 个正整数表示要选出的类型i的题数。这k个数相加就是要选出的总题数m。接下来的n行给出了题库中每个试题的类型信息。每行的第1 个正整数p表明该题可以属于p类,接着的p个数是该题所属的类型号。
输出格式:
第i 行输出 “i:”后接类型i的题号。如果有多个满足要求的方案,只要输出1个方案。如果问题无解,则输出“No Solution!”。
<<输入输出样例
输入样例#1:
3 15
3 3 4
2 1 2
1 3
1 3
1 3
1 3
3 1 2 3
2 2 3
2 1 3
1 2
1 2
2 1 2
2 1 3
2 1 2
1 1
3 1 2 3
输出样例#1:
1: 1 6 8
2: 7 9 10
3: 2 3 4 5
看到这个题目我们首先要收敛一下如此冗长的题目描述和样例输入(搞这么长怕是为了让我们好好研究一下样例)(好吧,其实这个题目长得很像二分图,并且这也确实可以做,但我们还是用网络流来做一遍吧)
- 每个题目只能充当一种题目类型使用,不能一个题目同时上多个类型(这也就解释了样例为什么所有题目所属类型想加其实要远多出一些)
二、善意的投票
<<问题描述
幼儿园里有n个小朋友打算通过投票来决定睡不睡午觉。对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神。虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以投和自己本来意愿相反的票。我们定义一次投票的冲突数为好朋友之间发生冲突的总数加上和所有和自己本来意愿发生冲突的人数。我们的问题就是,每位小朋友应该怎样投票,才能使冲突数最小?
<<输入输出格式
输入格式:
文件的第一行只有两个整数n,m,保证有2≤n≤300,1≤m≤n(n-1)/2。其中n代表总人数,m代表好朋友的对数。文件第二行有n个整数,第i个整数代表第i个小朋友的意愿,当它为1时表示同意睡觉,当它为0时表示反对睡觉。接下来文件还有m行,每行有两个整数i,j。表示i,j是一对好朋友,我们保证任何两对i,j不会重复。
输出格式:
只需要输出一个整数,即可能的最小冲突数。
<<输入输出样例
输入样例#1:
3 3
1 0 0
1 2
1 3
3 2
输出样例#1:
1
说明
2≤n≤300,1≤m≤n(n-1)/2。
这个题目并不是那么容易看出是个最小割的题目。
呈上一份代码(只有建模过程)
//规定S->反对的小朋友->同意的小朋友->T
int main()
{
read(n);read(m);int x,y;
S=0;T=n+1;
for(int i=1;i<=n;i++)
{
read(x);
if(x)link(i,T,1);
else link(S,i,1);
}
while(m--)
{
read(x);read(y);
link(x,y,1);
link(y,x,1);
}
dinic();
return 0;
}
相似例题推荐狼和羊的故事(两个题目几乎长得一模一样)
呈上一份代码(只有建模过程)
//规定S->羊->0->狼->T
int d[5][2]={{0,1},{1,0},{0,-1},{-1,0}};
int g[_][_];
int main()
{
read(n);read(m);
S=0;T=n*m+1;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)read(g[i][j]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(g[i][j]==1)link(S,ru(i,j),inf);
if(g[i][j]==2)link(ru(i,j),T,inf);
for(int k=0;k<4;k++)
{
if(i+d[k][0]<1||i+d[k][0]>n||j+d[k][1]<1||j+d[k][1]>m)continue;
if(g[i+d[k][0]][j+d[k][1]]!=1&&g[i][j]!=2)
link(ru(i,j),ru(i+d[k][0],j+d[k][1]),1);
}
}
}
dinic();
return 0;
}
三、修车(一个很恶心的费用流题)
题目描述:
同一时刻有N位车主带着他们的爱车来到了汽车维修中心。维修中心共有M位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的。现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。
说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。
经典思想:拆点(貌似费用流题目都需要我们拆点)将一个工人拆成N个,用于不同的阶段,因为工人不同的阶段进行修车是会影响我们对答案贡献的,而一共有M*N个工人需要连边,然后从S->Car->Career->T,跑遍费用流即可。
呈上一份代码,只有建模过程
样例输入
2 2
3 2
1 4
int main()
{
read(M);read(N);
S=0;T=N*M+N+1;int x;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
read(x);
for(int k=1;k<=N;k++)//阶段
{
link(M*N+i,ru(j,k),1,k*x);
//左边表示:要修理的车
//右边表示:左边表示第j人于倒数第k阶段修
}
}
}
for(int i=1;i<=N;i++)link(S,N*M+i,1,0);
for(int i=1;i<=N*M;i++)link(i,T,1,0);
bfs();
return 0;
}
附上两张图来供大家理解(用于样例,图略丑见谅)
初始图(没有标明费用)
跑完最大流的图(红色为匹配边,蓝色为最小费用)
一道很恶心的最小割
[国家集训队]happiness