前言:
网络流的题目最难的大概就是建模了吧,不过当你认真思考成功建立模型之后,又会有满满的成就感!!!
不多说,下面就让我们用题来感受网络流建模的魅力吧。
题目描述
这是一道模板题。
给定 n个点,m 条边,给定每条边的容量,求从点 s到点 t的最大流。
输入格式
第一行四个整数 n,m,s,t。
接下来的 m行,每行三个整数 u,v,c,表示 u到 v,流量为 c的一条边。
输出格式
输出点 s 到点 t的最大流。
样例输入
7 14 1 7
1 2 5
1 3 6
1 4 5
2 3 2
2 5 3
3 2 2
3 4 3
3 5 3
3 6 7
4 6 5
5 6 1
6 5 1
5 7 8
6 7 7
样例输出
14
数据范围与提示
1⩽n⩽100,1⩽m⩽5000,0⩽c⩽2^31-1
模板题,直接上代码:
#include
#define int long long
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e6+10;
struct edge{
int to,cap;
};
struct Dninc{
int n,m,s,t;
int d[N],cur[N];
vectorG[N];
vectoredges;
inline void add(int u,int v,int cap){
edges.push_back((edge){v,cap});
edges.push_back((edge){u,0});
int sz=edges.size();
G[u].push_back(sz-2);
G[v].push_back(sz-1);
}
inline void init(){
n=get(),m=get(),s=get(),t=get();
while(m--){
int u=get(),v=get(),cap=get();
add(u,v,cap);
}
}
inline bool bfs(){
memset(d,-1,sizeof(d));
queueQ;
Q.push(s);
d[s]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
int sz=G[x].size();
for(int i=0;i0){
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}return d[t]!=-1;
}
inline int dfs(int x,int Maxf){
if(x==t||Maxf==0)return Maxf;
int f,ret=0,sz=G[x].size();
for(int& i=cur[x];i0){
edges[G[x][i]^1].cap+=f;
e.cap-=f;
Maxf-=f;
ret+=f;
if(Maxf==0)break;
}
}return ret;
}
inline void solv(){
int flow=0;
while(bfs()){
memset(cur,0,sizeof(cur));
flow+=dfs(s,1<<30);
}cout<
2)网络流24题之飞行员搭配(loj#6000):
题目描述
飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两个驾驶员,需一个正驾驶员和一个副驾驶员。由于种种原因,例如相互配合的问题,有些驾驶员不能在同一架飞机上飞行,问如何搭配驾驶员才能使出航的飞机最多。
因为驾驶工作分工严格,两个正驾驶员或两个副驾驶员都不能同机飞行。
输入格式
第一行,两个整数 n 与 m,表示共有 n 个飞行员,其中有 m名飞行员是正驾驶员。下面有若干行,每行有 2个数字a、b。表示正驾驶员 a 和副驾驶员 b可以同机飞行。
注:正驾驶员的编号在前,即正驾驶员的编号小于副驾驶员的编号。
输出格式
仅一行一个整数,表示最大起飞的飞机数。
样例
样例输入
10 5
1 7
2 6
2 10
3 7
4 8
5 9
样例输出
4
数据范围与提示
2≤n≤10.
没错,这道题其实是个二分图最大匹配,当然我们可以直接二分图水过……但是,放在这里显然就表示……是的你没猜错!二分图最大匹配可以通过网络流实现!!!这就当是一道简单的练习题。
我们设1~n为正飞行员,n+1~n+m为副飞行员,每个正飞行员向汇点t连一条容量为1的边,表示正飞行员最多飞行一次,s连一条容量为1的边给每个副飞行员,同上,a和b之间的连线就表示两两匹配,连好边后跑一次网络流即可。
#include
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1010;
struct edge{
int to,cap;
};
struct Dinic{
int n,m,s,t;
int d[N],cur[N];
vectorG[N];
vectoredges;
inline void add(int u,int v,int cap){
edges.push_back((edge){v,cap});
edges.push_back((edge){u,0});
int sz=edges.size();
G[u].push_back(sz-2);
G[v].push_back(sz-1);
}
inline void init(){
n=get(),m=n-get(),n-=m,s=0,t=n+m+1;
for(int i=1;i<=m;i++)add(s,i+n,1);
int u,v;
while(cin>>u>>v)add(v,u,1);
for(int i=1;i<=n;i++)add(i,t,1);
}
inline bool bfs(){
memset(d,-1,sizeof(d));
queueQ;
Q.push(s);
d[s]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
int sz=G[x].size();
for(int i=0;i0){
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}return d[t]!=-1;
}
inline int dfs(int x,int Maxf){
if(x==t||Maxf==0)return Maxf;
int f,ret=0,sz=G[x].size();
for(int& i=cur[x];i0){
edges[G[x][i]^1].cap+=f;
e.cap-=f;
Maxf-=f;
ret+=f;
if(Maxf==0)break;
}
}return ret;
}
inline void solv(){
int flow=0;
while(bfs()){
memset(cur,0,sizeof(cur));
flow+=dfs(s,1<<30);
}cout<
3)pigs(poj#1149/bsoj#2536):
Description
尼克在一家养猪场工作,这家养猪场共有M间锁起来的猪舍,由于猪舍的钥匙都给了客户,所以尼克没有办法打开这些猪舍,客户们从早上开始一个接一个来购买生猪,他们到达后首先用手中的钥匙打开他所能打开的全部猪舍,然后从中选取他要买的生猪,尼克可以在此期间将打开的猪舍中的猪调整到其它开着的猪舍中,每个猪舍能存放的猪的数量是没有任何限制的。买完猪后客户会将他打开的猪舍关上。
好在尼克事先知道每位客户手中有哪些钥匙,要买多少猪,以及客户到来的先后次序。请你写一个程序,帮助尼克求出最多能卖出多少头生猪。
Input
输入文件的第一行包含两个整数M和N,1≤M≤1000,1≤N≤100,M为猪舍的数量,N为客户人数,猪舍的编号为1到M,客户的编号为1到N。
输入文件第二行包含M个空格隔开的整数,依次表示每个猪舍中的生猪数量,每个整数大于等于0,且小于等于1000。
接下来的N行每行表示一位客户的购买信息,第I个客户的购买信息位于第I+2行,其格式如下:
A K1 K2……KA B
它表示该客户共有A把钥匙,钥匙编号依次为K1,K2……KA,且K1 < K2 <……< KA,B为该客户要买的生猪的头数。
Output
输出文件仅有一行包含一个整数,表示尼克最多能卖出的生猪的头数。
Sample Input
3 3
3 1 10
2 1 2 2
2 1 3 3
1 2 6
Sample Output
7
这是一道非常经典的网络流最大流建模题,强烈建议写一写!!!
建模方法(引用自Edelweiss):
不难想象,这个问题的网络模型可以很直观地构造出来。就拿上面的例子来说,可以构造出图 1 所示的模型(图中凡是没有标数字的边,容量都是∞):
• 三个顾客,就有三轮交易,每一轮分别都有 3 个猪圈和 1 个顾客的结点。
• 从源点到第一轮的各个猪圈各有一条边,容量就是各个猪圈里的猪的初始数量。
• 从各个顾客到汇点各有一条边,容量就是各个顾客能买的数量上限。
• 在某一轮中,从该顾客打开的所有猪圈都有一条边连向该顾客,容量都是∞。
• 最后一轮除外,从每一轮的 i 号猪圈都有一条边连向下一轮的 i 号猪圈,容量都是∞,表示这一轮剩下的猪可以留到下一轮。
• 最后一轮除外,从每一轮被打开的所有猪圈,到下一轮的同样这些猪圈,两两之间都要连一条边,表示它们之间可以任意流通。
非常直观,但是poj的内存限制不允许我们开1000*1000个点,而且我测试过会超时,但还是建议写一写暴力建图,锻炼一下代码写作能力。当然,既然暴力建图方法已经出来,就一定可以更优化,是的!没错!可以“缩点”!!!下面我会给出两份代码,第一份是我的暴力构图,第二份是更巧妙的构图,碍于篇幅(其实是我懒啦,光是暴力就写得我心力憔悴),网上有很多第二份代码建图的解释。
//暴力
#include
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e6+10,inf = 0x3f3f3f3f;
struct edge{
int to,cap;
};
struct Dninc{
int n,m,s,t;
bool vis[1010];
int d[N],cur[N];
vectorG[N];
vectoredges;
inline void add(int u,int v,int cap){
edges.push_back((edge){v,cap});
edges.push_back((edge){u,0});
int sz=edges.size();
G[u].push_back(sz-2);
G[v].push_back(sz-1);
}
inline void init(){
m=get(),n=get();
s=0;t=n*m+n+1;
for(int i=1;i<=m;i++)add(s,i,get());
for(int i=1;i<=n;i++){
int num=get();
memset(vis,0,sizeof(vis));
vectorid;
id.clear();
for(int j=0;jQ;
Q.push(s);
d[s]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
int sz=G[x].size();
for(int i=0;i0){
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}return d[t]!=-1;
}
inline int dfs(int x,int Maxf){
if(x==t||Maxf==0)return Maxf;
int f,ret=0,sz=G[x].size();
for(int& i=cur[x];i0){
edges[G[x][i]^1].cap+=f;
e.cap-=f;
Maxf-=f;
ret+=f;
if(Maxf==0)break;
}
}return ret;
}
inline void solv(){
int flow=0;
while(bfs()){
memset(cur,0,sizeof(cur));
flow+=dfs(s,1<<30);
}cout<
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1010,inf = 0x3f3f3f3f;
struct edge{
int to,cap;
};
struct Dninc{
int n,m,s,t,pig[N],last[N];
int d[N],cur[N];
vectorG[N];
vectoredges;
inline void add(int u,int v,int cap){
edges.push_back((edge){v,cap});
edges.push_back((edge){u,0});
int sz=edges.size();
G[u].push_back(sz-2);
G[v].push_back(sz-1);
}
inline void init(){
m=get(),n=get();
s=0,t=n+1;
for(int i=1;i<=m;i++)pig[i]=get();
for(int i=1;i<=n;i++){
int num=get();
for(int j=1;j<=num;j++){
int id=get();
if(!last[id])add(s,i,pig[id]),last[id]=i;
else add(last[id],i,inf),last[id]=i;
}add(i,t,get());
}
}
inline bool bfs(){
memset(d,-1,sizeof(d));
queueQ;
Q.push(s);
d[s]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
int sz=G[x].size();
for(int i=0;i0){
d[e.to]=d[x]+1;
Q.push(e.to);
}
}
}return d[t]!=-1;
}
inline int dfs(int x,int Maxf){
if(x==t||Maxf==0)return Maxf;
int f,ret=0,sz=G[x].size();
for(int& i=cur[x];i0){
edges[G[x][i]^1].cap+=f;
e.cap-=f;
Maxf-=f;
ret+=f;
if(Maxf==0)break;
}
}return ret;
}
inline void solv(){
int flow=0;
while(bfs()){
memset(cur,0,sizeof(cur));
flow+=dfs(s,1<<30);
}cout<
知道你们肯定没耐心看完,那么,可以参见hwzer大佬的博客http://hzwer.com/5840.html
4)k-联赛(bsoj#2313):
Description
K-联赛职业足球俱乐部的球迷们都是有组织的训练有素的啦啦队员,就像红魔啦啦队一样(2002年韩日世界杯上韩国队的啦啦队)。这个赛季,经过很多场比赛以后,球迷们希望知道他们支持的球队是否还有机会赢得最后的联赛冠军。换句话说,球队是否可以通过某种特定的比赛结果最终取得最高的积分(获胜场次最多)。(允许出现多支队并列第一的情况。)
现在,给出每个队的胜负场数,wi和di,分别表示teami的胜场和负场(1≤i≤n)。还给出ai,j,表示teami和teamj之间还剩多少场比赛要进行(1≤i,j≤n)。这里,n表示参加联赛的队数,所有的队分别用1,2,…,n来编号。你的任务是找出所有还有可能获得冠军的球队。
所有队参加的比赛数是相同的,并且为了简化问题,你可以认为不存在平局(比赛结果只有胜或负两种)。
Input
第一行一个整数n(1≤n≤25),表示联赛中的队数。
第二行2n个数,w1,d1,w2,d2,……,wn,dn,所有的数不超过100。
第三行n2个数,a1,1,a1,2,…,a1,n,a2,1,…,a2,2,a2,n,…,an,1,an,2,…,an,n,所有的数都不超过10。ai,j=aj,i,如果i=j,则ai,j=0。
Output
仅一行,输出所有可能获得冠军的球队,按其编号升序输出,中间用空格分隔。
Sample Input
3
2 0 1 1 0 2
0 2 2 2 0 2 2 2 0
Sample Output
1 2 3
Hint
【样例2】
kleague.in
3
4 0 2 2 0 4
0 1 1 1 0 1 1 1 0
kleague.out
1 2
【样例3】
kleague.in
4
0 3 3 1 1 3 3 0
0 0 0 2 0 0 1 0 0 1 0 0 2 0 0 0
kleague.out
2 4
也是一道经典的问题,由于此题特殊性,赢一场得1分且没有平局,我们抓住这个性质来分析。
因为有n个队伍,所以假设有n*n场比赛(不是(n-1)*n)。
现在假设我们检查第i支队伍是否有可能胜利,我们当然会让其赢得所有其参加的比赛,那么得到第i支队伍打完比赛后的得分tot,显然如果有一支其他队伍比赢完所有比赛后第i支队伍的总得分tot还要大,则第i支队伍不可能是冠军。
否则,我们看图说话……
s连比赛,容量为a[i][j]表示还有多少场比赛要打,比赛连接队伍容量为+oo表示该队可任得p点分数,队伍连t,容量为tot-w[i],如果此时恰好满流,则说明比赛打完且各个队伍最终得分均没有超过tot,则第i支队就有可能是冠军,否则,必然不是冠军。
下面上代码:
#include
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1010;
struct edge{
int to,cap;
};
struct Dinic{
int n,s,t,w[N],a[N][N];
int d[N],cur[N];
vectorG[N];
vectoredges;
inline void add(int u,int v,int cap){
edges.push_back((edge){v,cap});
edges.push_back((edge){u,0});
int sz=edges.size();
G[u].push_back(sz-2);
G[v].push_back(sz-1);
}
inline void init(){
n=get(),s=0,t=n*n+n+1;
for(int i=1;i<=n;i++)w[i]=get(),get();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)a[i][j]=get();
}
inline bool bfs(){
memset(d,-1,sizeof(d));
queueQ;
Q.push(s);
d[s]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
int sz=G[x].size();
for(int i=0;i0)d[e.to]=d[x]+1,Q.push(e.to);
}
}return d[t]!=-1;
}
inline int dfs(int x,int Maxf){
if(x==t||Maxf==0)return Maxf;
int f,ret=0,sz=G[x].size();
for(int& i=cur[x];i0){
edges[G[x][i]^1].cap+=f;
e.cap-=f;
Maxf-=f;
ret+=f;
if(Maxf==0)break;
}
}return ret;
}
inline bool check(int idx){//枚举验证每一个答案
int tot=w[idx],full=0;
for(int i=1;i<=n;i++)tot+=a[idx][i];
for(int i=1;i<=n;i++)if(w[i]>tot)return 0;//特判可不写,但要考虑负权
edges.clear();
for(int i=0;i<=t;i++)G[i].clear();
for(int i=1;i