网络流例题

对于网络流的基础知识可以戳这里

我就不多bb了,讲的没人家好,还可能讲错( ఠൠఠ )ノ

一,模板题目:

1.P1343 地震逃生

2.P2740 [USACO4.2]草地排水Drainage Ditches

模板题就不贴代码了

二,初识最小割:

如果你对最小割了解的仅仅只是一个概念(最小割=最大流),那你可以看一下以下几张图帮你更加了解一下。

几张图了解网络流的割是个什么东西...:所谓网络流的割就是将点划分为连到S与连到T的两个点集合.....割就是这两个点集相连的边.....但注意...割的容量只记从S点集到T点集的....T点集到S点集的不算...所以割的容量等于这从S点集到T点集所有边的容量之和...而网络流的最小割就是这些割中容量最小的....

1.P4001 [ICPC-Beijing 2006]狼抓兔子

思路:裸题 最小割。vector存边dinic可能过不了,不是T最后一个点就是MLE,如果有用vector写的可以交流一下ε(┬┬﹏┬┬)3(一定是评测机的问题)

2.P1345 [USACO5.4]奶牛的电信Telecowmunication

思路:初步认识拆点,将求最小割点变成求最小割。将一个点拆成出点和入点,然后两点之间连边的权值是1,这样跑最小割,他割的是这条边实际是这个点。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data) memset(_data,0,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e3+5;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
il void add(int f,int t,int w) {
	eg[f].pb(edge {t,w,(int)eg[t].size()});
	eg[t].pb(edge {f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) 	return f;
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				e.cap -= d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int n,m,c1,c2;
int main() {
	std::ios::sync_with_stdio(0);
	cin>>n>>m>>c1>>c2;//除了起始点外,全部拆点
	for(int i=1;i<=n;++i){
		if(i!=c1 && i!=c2)	add(i,i+n,1);
	}	
	int a,b;
	for(int i=1; i<=m; ++i) { 
		cin>>a>>b;
		if(a!=c1 && a!=c2)	add(a+n,b,inf);
		else	add(a,b,inf);
		if(b!=c1 && b!=c2)	add(b+n,a,inf);
		else	add(b,a,inf);
	}
	cout<<dinic(c1,c2)<<endl;
	return 0;
}


 3.P1361 小M的作物 (最大流最小割)

思路:最小割就是花最少的力气使得所有的点分成两个集合,我们将A地看出源点s,B地看成汇点t,每个植物都可能向某一块地做出贡献,即A地向1有一条边,1向B有一条边。要选出一条边作为割边隔断。还有就是组合的情况了,新开一个点,s点指向新点为A贡献,新点指向他集合的点权值都为inf(使其不可能成为割边),B同理,答案就是收益总和-最小割(割掉的就是不要的收益),具体建图方式见图。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e5+5;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
il void add(int f,int t,int w) {
	eg[f].pb(edge {t,w,(int)eg[t].size()});
	eg[t].pb(edge {f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) 	return f;
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				e.cap -= d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int n,m,k; 
int main(){
	std::ios::sync_with_stdio(0);
	cin>>n;
	int tp,s=n+1,t=n+2,a,b,cnt=n+2;
	int sum=0;
	for(int i=1;i<=n;++i)	cin>>tp,add(s,i,tp),sum+=tp;
	for(int i=1;i<=n;++i)	cin>>tp,add(i,t,tp),sum+=tp;
	cin>>m;
	while(m--){
		cin>>k>>a>>b;
		sum+=a+b;
		int pa=++cnt,pb=++cnt;
		add(s,pa,a),add(pb,t,b);
		for(int i=1;i<=k;++i){
			cin>>tp;
			add(pa,tp,inf);
			add(tp,pb,inf);
		}
	}
	cout<<sum-dinic(s,t)<<endl;
	return 0;
}

三,拆点裂点 

1.P1231 教辅的组成

思路:要保证1本书只和一本教材和一本答案配对,即通过一本书的流量为1,故将书本拆成两个点。书的左边放练习册,右边放答案(反着放也无所谓),源点,汇点搞一搞就行了。建图详见洛谷第一篇题解图片,我的编号和图不一样。

å¾è§£

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data) memset(_data,0,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
il void add(int f,int t,int w) {
	eg[f].pb({t,w,(int)eg[t].size()});
	eg[t].pb({f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) 	return f;
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				e.cap -= d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int n1,n2,n3;
vector<int> mp[maxn]; 
int main(){
	std::ios::sync_with_stdio(0);
	cin>>n1>>n2>>n3;
	int tot=n1+n2+n3;
	int s=0,t=tot*10;
	for(int i=1;i<=n1;++i)	add(i,i+n1,1); //对书拆点 
	for(int i=n1*2+1;i<=n1*2+n2;++i)	add(s,i,1); //练习册 
	for(int i=n1*2+n2+1;i<=n1*2+n2+n3;++i)	add(i,t,1); //答案 
	
	int a,b,m;
	cin>>m;
	for(int i=0;i<m;++i){
		cin>>a>>b;
		add(b+n1*2,a,1);
	}
	cin>>m;
	for(int i=0;i<m;++i){
		cin>>a>>b;
		add(a+n1,b+n1*2+n2,1);
	}
	cout<<dinic(s,t)<<endl;
	return 0;
}

 2.P1402 酒店之王

思路:与上题相同。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data) memset(_data,0,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
il void add(int f,int t,int w) {
	eg[f].pb(edge {t,w,(int)eg[t].size()});
	eg[t].pb(edge {f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) 	return f;
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				e.cap -= d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int n,p,q;
int main(){
	std::ios::sync_with_stdio(0);
	cin>>n>>p>>q;
	int s=0,t=(n+p+q)*6;
	for(int i=1;i<=n;++i)	add(i,i+n,1); //人 
	for(int i=1+n*2;i<=n*2+p;++i)	add(s,i,1);	//房 
	for(int i=1+n*2+p;i<=q+n*2+p;++i)	add(i,t,1); //菜
	
	int tp; 
	for(int i=1;i<=n;++i){
		for(int j=1;j<=p;++j){
			cin>>tp;
			if(tp==1)	add(j+n*2,i,1);
		}
	} 
	for(int i=1;i<=n;++i){
		for(int j=1;j<=q;++j){
			cin>>tp;
			if(tp==1)	add(i+n,j+n*2+p,1);
		}
	}
	cout<<dinic(s,t)<<endl;
	return 0;
}

3.P2472 [SCOI2007]蜥蜴(略难)

思路:开始正儿巴金的建模了,任何时刻不能有两只蜥蜴在同一个石柱上,这句话是句屁话。用流的方式去思考,如果我们跳过去的石柱有蜥蜴,如果能往外跳,那可以让他先流出去,反之,一起流不去回到原来位置或者换方向跳,将每个点拆成两个点,他们之间的权值就是,石头的高度h,因为路过h个之后,石头的高度就会变成0,从而不能再有蜥蜴通过了。在能跳出矩阵的石头,都给他们的出点连一条边到t,权值为inf,而在能互通的点也连一条inf的边,在有蜥蜴的地方,从s连一条权值为1的边,然后跑最大流就行了。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data) memset(_data,0,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
il void add(int f,int t,int w) {
	eg[f].pb(edge {t,w,(int)eg[t].size()});
	eg[t].pb(edge {f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) 	return f;
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				e.cap -= d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int r,c,d;
int mp[25][25];
il int gid(int x,int y){
	return (x-1)*c+y;
}
il double js(double x1,double y1,double x2,double y2){
	return	(double)sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double len[25][25][25][25];
int main(){
	std::ios::sync_with_stdio(0);
	cin>>r>>c>>d;
	for(int i=1;i<=r;++i){
		for(int j=1;j<=c;++j){
			for(int k=1;k<=r;++k){
				for(int t=1;t<=c;++t){
					len[i][j][k][t]=js(i,j,k,t);
				}
			}
		}
	}
	int s=0,t=r*c*10; 
	char tp;
	int num;
	for(int i=1;i<=r;++i){
		for(int j=1;j<=c;++j){
			cin>>tp;
			num=tp-'0'; //题目没说不超过10,但数据是不超过10的
			add(gid(i,j),gid(i,j)+r*c,num); //拆点,容量为高度 
			if(i+d>r || i-d<1 || j+d>c || j-d<1 ){
				add(gid(i,j)+r*c,t,inf); //可以跳出去,向汇点连边 
			}
			int up=max(1,i-d),low=min(i+d,r),le=max(j-d,1),ri=min(c,j+d);
			for(int k=up;k<=low;++k){ //向可能跳出的点连边 
				for(int t=le;t<=ri;++t){
					if(len[i][j][k][t]<=d)	add(gid(i,j)+r*c,gid(k,t),inf);
				} 
			} 
			
		}
	}
	int sum=0;
	for(int i=1;i<=r;++i){
		for(int j=1;j<=c;++j){
			cin>>tp;
			if(tp=='L')	add(s,gid(i,j),1),sum++;	
		}
	} 
	cout<<sum-dinic(s,t)<<endl;
	return 0;
}

4.P2944 [USACO09MAR]地震损失2Earthquake Damage 2

思路:问题转化建模,拆点变成求最小割。题目就是说有些村庄不能到牛棚,而有些能到,那不就是将集合分成两个部分嘛,就是我们最少破坏几个村庄使得已知条件满足,就是拆点,求最小割(就是求最小割点p1345)。对于已经确定没坏但不能到达牛棚的点,我们给他的两点之间连inf,并且将它的出点连inf到t,因为他必不能到牛棚s(这点需注意),起点和汇点也是确定的点,不能被割。而对于不确定的点就给他的两点之间连1.最后跑个最小割。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data) memset(_data,0,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
il void add(int f,int t,int w) {
	eg[f].pb(edge {t,w,(int)eg[t].size()});
	eg[t].pb(edge {f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) 	return f;
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				e.cap -= d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int p,c,n;
bool vis[maxn];
int main(){
	std::ios::sync_with_stdio(0);
	cin>>p>>c>>n;
	int s=1,t=p*10,a,b;
	for(int i=1;i<=c;++i){
		cin>>a>>b;
		add(a+p,b,inf),add(b+p,a,inf); 
	}
	for(int i=1;i<=n;++i){
		cin>>a;
		add(a,a+p,inf),vis[a]=1;
		add(a+p,t,inf);
	}
	add(s,s+p,inf),add(s+p,s,inf);
	add(t,t+p,inf),add(t+p,t,inf);
	for(int i=2;i<=p;++i){
		if(!vis[i])	add(i,i+p,1);
	}
	cout<<dinic(s,t)<<endl;
	return 0;
}


5.P2764 最小路径覆盖问题最小路径覆盖=原图的结点数-新图的最大匹配数

思路:我们可以先认为每个点都是一个路径,我们每次将两条路径合并就会少一条路径,当然又向无环图,点只能在一条路径中出现,所以经过每个点的流量只能为1。将每个点拆点为出点和入点,s到每个出点的值为1,入点到t的值为1,保证每个点经过的流量为1,然后再给能到的点,连一下出点到入点(就是个二分图,出点在左,入点在右),这样每个点都只能选一个方向流出,也只能选一个方向流入。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数,这是一个定理,不理解但要记住。输出路径的话,我们只需要知道头头,然后记录下一个。还有就是这题前向星,比vector快10倍。

//两种代码
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data) memset(_data,0,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
int nxt[maxn];
int n,m,s,t;
vector<int> head;
struct edge {
	int to, cap, rev;
};
vector<edge> eg[maxn];
int dep[maxn], arc[maxn];
bool vis[maxn];
il void add(int f,int t,int w) {
	eg[f].pb(edge {t,w,(int)eg[t].size()});
	eg[t].pb(edge {f,0,(int)eg[f].size()-1});
}
il void bfs(int s) {
	memset(dep, -1, sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	int v;
	while(!q.empty()) {
		v=q.front();
		q.pop();
		for (int i=0; i<(int)eg[v].size(); ++i) {
			edge &e=eg[v][i];
			if (e.cap>0 && dep[e.to]==-1) {
				dep[e.to]=dep[v]+1;
				q.push(e.to);
			}
		}
	}
}
il int dfs(int v,int t,int f) {
	if (v==t) {
		return f;
	}
	int d;
	for(int &i=arc[v]; i<(int)eg[v].size(); ++i) {
		edge &e=eg[v][i];
		if (e.cap>0 && dep[v]+1==dep[e.to]) {
			d=dfs(e.to,t,min(f, e.cap));
			if (d>0) {
				nxt[v]=e.to; //存一下当前节点的下一个
				if(v!=s)	vis[e.to-n]=1; //不是头节点,置为1
				
				e.cap-=d;
				eg[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int dinic(int s,int t) {
	int flow = 0;
	int d;
	while(1) {
		bfs(s);
		if (dep[t]==-1)	return flow;
		memset(arc,0,sizeof(arc));
		while ((d=dfs(s,t,inf))>0) {
			flow+=d;
		}
	}
}
int main() {
	std::ios::sync_with_stdio(0);
	cin>>n>>m;
	s=0,t=n*10;
	int x,y;
	for(int i=1; i<=n; ++i) {
		add(s,i,1),add(i+n,t,1);
	}
	for(int i=1; i<=m; ++i) {
		cin>>x>>y;
		add(x,y+n,1);
	}
	int ans=n-dinic(s,t);
	for(int i=1; i<=n; ++i) {
		if(!vis[i]) { //是头节点,就一直输出到尾
			int now=i;
			cout<<now<<" ";
			while(nxt[now] && nxt[now]!=t){
				cout<<nxt[now]-n<<" ";
				now=nxt[now]-n;
			}
			cout<<endl;
		}
	}
	cout<<ans<<endl;
	return 0;
}





#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define rg register
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
int dep[maxn],hd[maxn],cur[maxn],cnt=1;
int n,m,s,t;
bool vis[maxn]; 
int nxt[maxn];
struct edge {
	int to,nxt,c;
} eg[maxn];
il void add(int f,int t,int w) {
	eg[++cnt]=edge{t,hd[f],w},hd[f]=cnt;
	eg[++cnt]=edge{f,hd[t],0},hd[t]=cnt;
}
queue<int> q;
il bool bfs(int s,int t) {
	for(int i=s;i<=t;++i)	dep[i]=0;
	while(!q.empty())	q.pop();
	q.push(s),dep[s]=1;
	int now,to;
	while(!q.empty()) {
		now=q.front(),q.pop();
		for(int i=hd[now]; i; i=eg[i].nxt) {
			to=eg[i].to;
			if(!dep[to] && eg[i].c>0) {
				dep[to]=dep[now]+1;
				q.push(to);
			}
		}
	}
	return dep[t];
}
il int dfs(int v,int t,int flow) {
	if(v==t)	return flow;
	int to,tp;
	for(int &i=cur[v]; i; i=eg[i].nxt){
		to=eg[i].to;
		if(eg[i].c>0 && dep[to]==dep[v]+1){
			tp=dfs(to,t,min(flow,eg[i].c));
			if(tp>0){
				nxt[v]=to;
				if(v!=s)	vis[to-n]=1;
				
				eg[i].c-=tp,eg[i^1].c+=tp;
				return tp;
			}
		}
	}
	return 0;
}
il int dinic(int s,int t){
	int flow=0;
	while(bfs(s,t)){
		for(int i=s;i<=t;++i)	cur[i]=hd[i];  
		while(int tp=dfs(s,t,inf))	flow+=tp;
	}
	return flow;
}

int main() {
	std::ios::sync_with_stdio(0);
	cin>>n>>m;
	s=0,t=n*10;
	int x,y;
	for(int i=1; i<=n; ++i) {
		add(s,i,1),add(i+n,t,1);
	}
	for(int i=1; i<=m; ++i) {
		cin>>x>>y;
		add(x,y+n,1);
	}
	int ans=n-dinic(s,t);
	for(int i=1; i<=n; ++i) {
		if(!vis[i]) {
			int now=i;
			cout<<now<<" ";
			while(nxt[now] && nxt[now]!=t){
				cout<<nxt[now]-n<<" ";
				now=nxt[now]-n;
			}
			cout<<endl;
		}
	}
	cout<<ans<<endl;
	return 0;
}

6.P2765 魔术球问题

思路:此题就是上题的变种。就是一堆数,然后他们有的数之间有平方的关系(有边),然后你用最小路径覆盖,路径就是柱子,知道最小路径覆盖=原图的结点数-新图的最大匹配数,这个我们是知道的。点我们一个一个的加,每次加都给他的入点和t连权值为1的边,s和出点连边。然后在给可以和他组成平方数的点之间连一条权值为1的边,跑最大流,得到最小路径数如果大于柱子数,那就直接停了,而且我们知道最大的数是比现在这个数小1.再用上面的方法输出路径。

#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define rg register
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
int dep[maxn],hd[maxn],cur[maxn],cnt=1;
int num,p=10000,nxt[maxn];
bool pd[maxn],vis[maxn];
int s,t;
struct edge {
	int to,nxt,c;
} eg[maxn];
il void add(int f,int t,int w) {
	eg[++cnt]= {t,hd[f],w},hd[f]=cnt;
	eg[++cnt]= {f,hd[t],0},hd[t]=cnt;
}
queue<int> q;
il bool bfs(int s,int t) {
	for(int i=s; i<=t; ++i)	dep[i]=0;
	while(!q.empty())	q.pop();
	q.push(s),dep[s]=1;
	int now,to;
	while(!q.empty()) {
		now=q.front(),q.pop();
		for(int i=hd[now]; i; i=eg[i].nxt) {
			to=eg[i].to;
			if(!dep[to] && eg[i].c>0) {
				dep[to]=dep[now]+1;
				q.push(to);
			}
		}
	}
	return dep[t];
}
il int dfs(int v,int t,int flow) {
	if(v==t)	return flow;
	int to,tp;
	for(int &i=cur[v]; i; i=eg[i].nxt) {
		to=eg[i].to;
		if(eg[i].c>0 && dep[to]==dep[v]+1) {
			tp=dfs(to,t,min(flow,eg[i].c));
			if(tp>0) {
				nxt[v]=to;
				if(v!=s)	vis[to-p]=1;
				eg[i].c-=tp,eg[i^1].c+=tp;
				return tp;
			}
		}
	}
	return 0;
}
il void dinic(int s,int t) {
	int flow=0;
	int i;
	for(i=1; ; ++i) {
		add(s,i,1),add(i+p,t,1);
		for(int j=1; j<i; ++j) {
			if(pd[i+j])	add(j,i+p,1);
		}
		while(bfs(s,t)) {
			for(int i=s; i<=t; ++i)	cur[i]=hd[i];
			while(int tp=dfs(s,t,inf))	flow+=tp;
		}
		if(i-flow>num)	break;
	}
	cout<<i-1<<endl;
	for(int j=1; j<i; ++j) {
		if(!vis[j]) {
			int now=j;
			cout<<now<<" ";
			while(nxt[now] && nxt[now]!=t) {
				cout<<nxt[now]-p<<" ";
				now=nxt[now]-p;
			}
			cout<<endl;
		}
	}

}
int main() {
	std::ios::sync_with_stdio(0);
	cin>>num;
	for(int i=2; i*i<maxn; ++i)	pd[i*i]=1;
	s=0,t=100000;
	dinic(s,t);
	return 0;
}

四,二分图匹配(匈牙利/网络流)

了解匈牙利有趣易懂

二分图最大匹配 变种:(匹配数量)
1.最小顶点覆盖 = 最大匹配数
在二分图中求最少的点,让每条边都至少和其中的一个点关联,这就是:“二分图的最小顶点覆盖”
二分图的最小顶点覆盖数=最大匹配数
2.最小路径覆盖 = 节点数(n)- 最大匹配数(m)
用尽量少的不相交简单路径覆盖有向无环图(DAG)的所有顶点,这就是DAG图的最小路径覆盖问题
DAG图的最小路径覆盖数 = 节点数(n)- 最大匹配数(m)
3.二分图最大独立集 = 节点数(n)- 最大匹配数(m)
从无向图中的顶点中选出k个并且k个顶点之间互不相邻,最大的k就是最大独立集
二分图的最大独立集数 = 节点数(n)- 最大匹配数(m)

1.P2756 飞行员配对方案问题

思路:二分图匹配裸题。也可以网络流搞一下,差不多的速度,但明显匈牙利更加好写。

//匈牙利
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
bool mp[maxn][maxn],vis[maxn];
int match[maxn];
int k,m,n;
il bool dfs(int x,int n){
	for(int i=1;i<=n;++i){
		if(mp[x][i] && !vis[i]){
			vis[i]=1;
			if(match[i]==0 || dfs(match[i],n)){
				match[i]=x;
				return 1;
			}
		}
	}
	return 0;
}
il int solve(int m,int n){ 
	int ans=0;
	for(int i=1;i<=m;++i){
		ms(vis,0);
		if(dfs(i,n))	ans++;
	}
	return ans;
}
int main(){
	std::ios::sync_with_stdio(0);
	ms(match,0);
	cin>>m>>n;
	int x,y;
	while(cin>>x>>y && (x!=-1 && y!=-1)){
		mp[x][y-m]=1;
	}
	cout<<solve(m,n)<<endl; 
	for(int i=1;i<=n;++i){
		if(match[i])	cout<<match[i]<<" "<<i+m<<endl;
	}
	return 0;
}



//网络流
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define rg register
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
int dep[maxn],hd[maxn],cur[maxn],cnt=1;
int match[maxn];
int n,m,s,t;
struct edge {
	int to,nxt,c;
} eg[maxn];
il void add(int f,int t,int w) {
	eg[++cnt]=edge{t,hd[f],w},hd[f]=cnt;
	eg[++cnt]=edge{f,hd[t],0},hd[t]=cnt;
}
queue<int> q;
il bool bfs(int s,int t) {
	for(int i=s;i<=t;++i)	dep[i]=0; 
	while(!q.empty())	q.pop();
	q.push(s),dep[s]=1;
	int now,to;
	while(!q.empty()) {
		now=q.front(),q.pop();
		for(int i=hd[now]; i; i=eg[i].nxt) {
			to=eg[i].to;
			if(!dep[to] && eg[i].c>0) {
				dep[to]=dep[now]+1;
				q.push(to);
			}
		}
	}
	return dep[t];
}
il int dfs(int v,int t,int flow) {
	if(v==t)	return flow;
	int to,tp;
	for(int &i=cur[v]; i; i=eg[i].nxt){
		to=eg[i].to;
		if(eg[i].c>0 && dep[to]==dep[v]+1){
			tp=dfs(to,t,min(flow,eg[i].c));
			if(tp>0){
				if(v!=s && to!=t)	match[v]=to;
				eg[i].c-=tp,eg[i^1].c+=tp;
				return tp;
			}
		}
	}
	return 0;
}
il int dinic(int s,int t){
	int flow=0;
	while(bfs(s,t)){
		for(int i=s;i<=t;++i)	cur[i]=hd[i]; 
		while(int tp=dfs(s,t,inf))	flow+=tp;
	}
	return flow;
}
int main() {
	std::ios::sync_with_stdio(0);
	cin>>m>>n;
	s=0,t=m+n+1;
	int x,y;
	for(int i=1;i<=m;++i)	add(s,i,1);
	for(int i=m+1;i<=n+m;++i)	add(i,t,1);
	while(cin>>x>>y && (x!=-1 && y!=-1)){
		add(x,y,1);
	}
	cout<<dinic(s,t)<<endl;
	for(int i=1;i<=m;++i){
		if(match[i])	cout<<i<<" "<<match[i]<<endl;
	}
	return 0;
}

2.P2055 [ZJOI2009]假期的宿舍

思路:讲两种建模方式吧。

网络流建模:对于每个点拆点(也可以认为一边是人,一边是床),对于在校生,从他们的床连一条权值为1的边到汇点t,而对于不回家的在校生或者非校生(这些都是想要住学校的人),从源点连一条权值为1的边到他们,同时不回家的在校生当然也可以睡自己床了,连一条到自己的床。然后就是认识的关系,i认识j就可以从i连一条到j的床的边。
匈牙利建模:不回家而且是在校生,mp[i][i]=1,可以和自己的床匹配。对于有关系的,不回家的在校生或者不是在校生,并且对方是在校生,建立mp[i][j]=1,此二分图要保证左边的人都需要床住,而右边的都有床才能匹配,否则建立的边就是无效的(不要床的你给他连边出来,可能就会多分配,对于没有床的,你连过来也可能多匹配)。

//网络流
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define rg register
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e6+5;
int dep[maxn],hd[maxn],cur[maxn],cnt=1;
struct edge {
	int to,nxt,c;
} eg[maxn];
il void add(int f,int t,int w) {
	eg[++cnt]=edge{t,hd[f],w},hd[f]=cnt;
	eg[++cnt]=edge{f,hd[t],0},hd[t]=cnt;
}
queue<int> q;
il bool bfs(int s,int t) {
	for(int i=s;i<=t;++i)	dep[i]=0; 
	while(!q.empty())	q.pop();
	q.push(s),dep[s]=1;
	int now,to;
	while(!q.empty()) {
		now=q.front(),q.pop();
		for(int i=hd[now]; i; i=eg[i].nxt) {
			to=eg[i].to;
			if(!dep[to] && eg[i].c>0) {
				dep[to]=dep[now]+1;
				q.push(to);
			}
		}
	}
	return dep[t];
}
il int dfs(int v,int t,int flow) {
	if(v==t)	return flow;
	int to,tp;
	for(int &i=cur[v]; i; i=eg[i].nxt){
		to=eg[i].to;
		if(eg[i].c>0 && dep[to]==dep[v]+1){
			tp=dfs(to,t,min(flow,eg[i].c));
			if(tp>0){
				eg[i].c-=tp,eg[i^1].c+=tp;
				return tp;
			}
		}
	}
	return 0;
}
il int dinic(int s,int t){
	int flow=0;
	while(bfs(s,t)){
		for(int i=s;i<=t;++i)	cur[i]=hd[i];  
		while(int tp=dfs(s,t,inf))	flow+=tp;
	}
	return flow;
}
int n,m,s,t,T;
int sch[maxn];
int main() {
	std::ios::sync_with_stdio(0);
	cin>>T;
	int tp;
	while(T--){
		cin>>n; 
		s=0,t=n*2+5;
		int num=0;
		for(int i=1;i<=n;++i){ //在校生 
			cin>>sch[i];
			if(sch[i]==1)	add(i+n,t,1); 
		} 
		for(int i=1;i<=n;++i){ //回家 
			cin>>tp;
			if((tp==0 && sch[i]==1) || (sch[i]==0))	add(s,i,1),num++;
			//不回家而且是在校生 或者 不是在校生
			if(tp==0 && sch[i]==1)	add(i,i+n,1);
			//不回家的在校生当然也可以睡自己床了	
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				cin>>tp;
				if(tp==1 && i!=j)	add(i,j+n,1);
			}
		}
		int res=dinic(s,t);
		if(res>=num)	cout<<"^_^"<<endl;
		else	cout<<"T_T"<<endl;
		cnt=1; 
		for(int i=s;i<=t;++i)	hd[i]=0;
	}
	return 0;
}



//匈牙利
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e2+5;
bool mp[maxn][maxn],vis[maxn];
int match[maxn];
int n,sch[maxn],back[maxn];
il bool dfs(int x,int n){
	for(int i=1;i<=n;++i){
		if(mp[x][i] && !vis[i]){
			vis[i]=1;
			if(match[i]==0 || dfs(match[i],n)){
				match[i]=x;
				return 1;
			}
		}
	}
	return 0;
}
il int solve(int m,int n){ 
	int ans=0;
	for(int i=1;i<=m;++i){
		ms(vis,0);
		if(dfs(i,n))	ans++;
	}
	return ans;
}
int T;
int main(){
	std::ios::sync_with_stdio(0);
	cin>>T;
	int tp; 
	while(T--){
		cin>>n;
		int tot=0;
		for(int i=1;i<=n;++i)	cin>>sch[i];	//在校生 
		for(int i=1;i<=n;++i){	//回家 
			cin>>back[i];
			if(back[i]==0 && sch[i]==1)	mp[i][i]=1;
			//不回家而且是在校生
			if((back[i]==0 && sch[i]==1) || sch[i]==0)	tot++; 
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=n;++j){
				cin>>tp;
				if(tp==1 && i!=j){
					if(((sch[i]==1 && back[i]==0) || sch[i]==0) && sch[j]==1)	mp[i][j]=1;
					//不回家而且是在校生或者不是在校生,并且对方是在校生 
				}
			}
		} 
		int num=solve(n,n);
		if(num>=tot) 	cout<<"^_^"<<endl;
		else	cout<<"T_T"<<endl;
		ms(back,0),ms(sch,0),ms(mp,0),ms(vis,0),ms(match,0);
	}
	
	return 0;
}

五,最小费用最大流(Dijkstra稠密图/Spfa稀疏图)

1.P4016 负载平衡问题

思路:最后都是一样的数量,那先将每一个仓库的数量先减去平均数,看看要往外搬出去多少个,搬进来多少个,源点s向每个是正值(假设现仓库为v)的仓库连一条权值为v的边,说明要从他这总共要流出v的流量,再对每个是负值的仓库(假设现仓库为-u),连一条值为u的边到t,它差u的流量所以流出u的流量到t,说明它得到了u的流量。再对相邻的点连权值为inf的边。单位流量的费用为1.

//spfa
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=1e5+5;
using namespace std;
struct edge {
	int to,cap,cost,rev;
};
vector<edge> eg[maxn];
int dis[maxn],pre[maxn],preid[maxn];
bool inq[maxn];
int n,m,s,t;

int flow=0,cost=0;
void init() {
	for(int i=0; i<=t; ++i)	pre[i]=preid[i]=inq[i]=0,eg[i].clear();
    flow = 0, cost = 0;
}
il void add(int f,int t,int w,int c) {
	eg[f].pb(edge {t,w,c,(int)eg[t].size()});
	eg[t].pb(edge {f,0,-c,(int)eg[f].size()-1});
}
void spfa(int st) {
	for(int i=0; i<=t; ++i)	dis[i]=inf;
	dis[st]=0,inq[st]=1;
	queue<int> q;
	q.push(st);
	int u;
	edge e;
	while(!q.empty()) {
		u=q.front(),q.pop();
		inq[u]=0;
		for(int i=0; i<(int)eg[u].size(); ++i) {
			e=eg[u][i];
			if(e.cap>0 && dis[e.to]>dis[u]+e.cost) {
				dis[e.to]=dis[u]+e.cost, pre[e.to]=u, preid[e.to]=i;
				if(!inq[e.to]) {
					q.push(e.to);
					inq[e.to]=1;
				}
			}
		}
	}
}
void costflow(int s,int t) {
	int d;
	while(1) {
		spfa(s);
		if(dis[t]==inf)	break;
		d=inf;
		for(int v=t; v!=s; v=pre[v]) {
			d=min(d,eg[pre[v]][preid[v]].cap);
		}
		flow+=d,cost+=d*dis[t];
		for(int v=t; v!=s; v=pre[v]) {
			eg[pre[v]][preid[v]].cap-=d;
			eg[v][eg[pre[v]][preid[v]].rev].cap+=d;
		}
	}
}
int a[maxn];
int main() {
	std::ios::sync_with_stdio(0);
	cin>>n;
	int sum=0,ave;
	for(int i=1;i<=n;++i)	cin>>a[i],sum+=a[i];
	ave=sum/n;
	s=0,t=n+1;
	for(int i=1; i<=n; ++i) {
		a[i]-=ave;
		if(a[i]>0)	add(s,i,a[i],0);
		if(a[i]<0)	add(i,t,-a[i],0);

		if(i==1)	add(i,i+1,inf,1),add(i,n,inf,1);
		else	if(i==n)	add(i,i-1,inf,1),add(i,1,inf,1);
		else	add(i,i+1,inf,1),add(i,i-1,inf,1);
	}
	costflow(s,t);
	cout<<cost<<endl;
	return 0;
}





//Dijkstra
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=1e5+5;
struct edge {
	int to, cap, cost, rev;
};
vector<edge> G[maxn];
int dis[maxn],pre[maxn], prid[maxn],h[maxn];
int n,s,t;
int flow=0,cost=0;
void init() {
	for(int i=0; i<=t; ++i)	pre[i]=prid[i]=h[i]=0,G[i].clear();
    flow=0,cost=0;
} //暂默认t坐标最大
il void add(int f,int t,int w,int c) {
	G[f].pb(edge {t,w,c,(int)G[t].size()});
	G[t].pb(edge {f,0,-c,(int)G[f].size()-1});
}
il bool dijkstra(int st) {
	struct pdis {
		int p,val;
		bool operator <(const pdis &pd) const {
			return val>pd.val;
		}
	};
	for(int i=0; i<=t; ++i)	dis[i]=inf;
	dis[st]=0;
	priority_queue<pdis> q;
	q.push({st,dis[st]});
	int now;
	while (!q.empty()) {
		now=q.top().p,q.pop();
		for (int i=0; i<(int)G[now].size(); ++i) {
			edge &e=G[now][i];
			if (e.cap>0 && dis[e.to]>dis[now]+e.cost+h[now]-h[e.to]) {
				dis[e.to]=dis[now]+e.cost+h[now]-h[e.to];
				pre[e.to]=now,prid[e.to]=i;
				q.push({e.to,dis[e.to]});
			}
		}
	}
	return dis[t]!=inf;
}
void costflow(int s,int t) {
	while (dijkstra(s)) {
		for(int i=0; i<=t; ++i)	h[i]+=dis[i];
		int d=inf;
		for(int v=t; v!=s; v=pre[v])	d=min(d,G[pre[v]][prid[v]].cap);
		flow+=d,cost+=d*h[t];
		for(int v=t; v!=s; v=pre[v]) {
			G[pre[v]][prid[v]].cap-=d;
			G[v][G[pre[v]][prid[v]].rev].cap+=d;
		}
	}
}
int a[maxn];
int main() {
	std::ios::sync_with_stdio(0);
	cin>>n;
	int sum=0,ave;
	for(int i=1; i<=n; ++i)	cin>>a[i],sum+=a[i];
	ave=sum/n;
	s=0,t=n+1;
	for(int i=1; i<=n; ++i) {
		a[i]-=ave;
		if(a[i]>0)	add(s,i,a[i],0);
		if(a[i]<0)	add(i,t,-a[i],0);

		if(i==1)	add(i,i+1,inf,1),add(i,n,inf,1);
		else	if(i==n)	add(i,i-1,inf,1),add(i,1,inf,1);
		else	add(i,i+1,inf,1),add(i,i-1,inf,1);
	}
	costflow(s,t);
	cout<<cost<<endl;
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值