网络流入门之简单建模(2)

前言:

网络流的题目最难的大概就是建模了吧,不过当你认真思考成功建立模型之后,又会有满满的成就感!!!

不多说,下面就让我们用题来感受网络流建模的魅力吧。

题目:

1)最大流模板题(loj#101):

题目描述

这是一道模板题。

给定 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<bits/stdc++.h>
#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];
	vector<int>G[N];
	vector<edge>edges;
	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));
		queue<int>Q;
		Q.push(s);
		d[s]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			int sz=G[x].size();
			for(int i=0;i<sz;i++){
				edge e=edges[G[x][i]];
				if(d[e.to]==-1&&e.cap>0){
					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];i<sz;i++){
			edge &e=edges[G[x][i]];
			if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(Maxf,e.cap)))>0){
				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<<flow<<"\n";
	}
}z;
main(){
	z.init();
	z.solv();
	return 0;
}

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<bits/stdc++.h>
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];
	vector<int>G[N];
	vector<edge>edges;
	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));
		queue<int>Q;
		Q.push(s);
		d[s]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			int sz=G[x].size();
			for(int i=0;i<sz;i++){
				edge e=edges[G[x][i]];
				if(d[e.to]==-1&&e.cap>0){
					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];i<sz;i++){
			edge &e=edges[G[x][i]];
			if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(Maxf,e.cap)))>0){
				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<<flow<<"\n";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;
}

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<bits/stdc++.h>
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];
	vector<int>G[N];
	vector<edge>edges;
	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));
			vector<int>id;
			id.clear();
			for(int j=0;j<num;j++){
				id.push_back(get());
				vis[id[j]]=1;
			}
			int sz=id.size();
			if(i!=n){
				for(int j=0;j<sz;j++)
					for(int k=0;k<sz;k++){
						add(id[j]+(i-1)*m,id[k]+i*m,inf);
					}
				for(int j=1;j<=m;j++)if(!vis[j])add(j+(i-1)*m,j+i*m,inf);
			}
			for(int j=0;j<sz;j++)add(id[j]+(i-1)*m,n*m+i,inf);
			add(n*m+i,n*m+n+1,get());
		}
	}
	inline bool bfs(){
		memset(d,-1,sizeof(d));
		queue<int>Q;
		Q.push(s);
		d[s]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			int sz=G[x].size();
			for(int i=0;i<sz;i++){
				edge e=edges[G[x][i]];
				if(d[e.to]==-1&&e.cap>0){
					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];i<sz;i++){
			edge &e=edges[G[x][i]];
			if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(Maxf,e.cap)))>0){
				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<<flow<<"\n";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;
}
/******************************/
//巧构 
#include<bits/stdc++.h>
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];
	vector<int>G[N];
	vector<edge>edges;
	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));
		queue<int>Q;
		Q.push(s);
		d[s]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			int sz=G[x].size();
			for(int i=0;i<sz;i++){
				edge e=edges[G[x][i]];
				if(d[e.to]==-1&&e.cap>0){
					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];i<sz;i++){
			edge &e=edges[G[x][i]];
			if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(Maxf,e.cap)))>0){
				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<<flow<<"\n";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;
}

知道你们肯定没耐心看完,那么,可以参见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<bits/stdc++.h>
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];
	vector<int>G[N];
	vector<edge>edges;
	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));
		queue<int>Q;
		Q.push(s);
		d[s]=0;
		while(!Q.empty()){
			int x=Q.front();Q.pop();
			int sz=G[x].size();
			for(int i=0;i<sz;i++){
				edge e=edges[G[x][i]];
				if(d[e.to]==-1&&e.cap>0)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];i<sz;i++){
			edge& e=edges[G[x][i]];
			if(d[e.to]==d[x]+1&&(f=dfs(e.to,min(e.cap,Maxf)))>0){
				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<n;i++)if(i!=idx)
			for(int j=i+1;j<=n;j++)if(j!=idx)
				full+=a[i][j],
				add(s,(i-1)*n+j,a[i][j]),
				add((i-1)*n+j,n*n+i,a[i][j]),
				add((i-1)*n+j,n*n+j,a[i][j]);
		for(int i=1;i<=n;i++)if(i!=idx)
			add(n*n+i,t,tot-w[i]);
		int flow=0;
		while(bfs()){
			memset(cur,0,sizeof(cur));
			flow+=dfs(s,1<<30);
		}return flow==full;
	}
	inline void solv(){
		for(int i=1;i<=n;i++)
			if(check(i))cout<<i<<" ";
	}
}z;
int main(){
	z.init();
	z.solv();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/dancingz/article/details/80320981