网络流24题

已做

未做

P4011

留坑

P2756

Link

裸的网络流,当然也可以用二分图匹配

首先讲讲怎么建图(好像也不用怎么讲)

就以样例为例,有5个外籍飞行员(编号\(1\)$5$)和5个英国飞行员(编号$6$\(10\)),对于每一个可以配对的飞行员,就在他们两个之间连一条边。然后源点\(s\)连所有外籍飞行员,汇点\(t\)连英国飞行员,这样就保证了一定是外籍飞行员和英国飞行员搭配。因为每个飞行员只能配对一个飞行员,所以不妨让每条边的最大容量都为\(1\)。于是图就建完了。

由于每条边的最大容量都为\(1\),所以如果一个流能从\(s\)流到\(t\),那么流最终的大小必定为\(1\),所以图的最大流就是能派出最多的飞机数量。然后在跑完最大流后遍历每一条边,看它的流是否大于\(0\)(从高处流到低处),如果是就说明起点和终点最终可以匹配,就输出这条边的起点和终点。值得一提的是,因为题目只要求问飞行员的匹配情况,所以得排除连向\(s\)\(t\)的边。

代码:

#include<bits/stdc++.h>
using namespace std;
using namespace my_std;
ll m,n,x,y,s,t,head[10001],dep[10001],cnt=1,ans=0;
struct node{
	ll nxt,to,w;
}e[10001];
void add(ll u,ll v,ll w){
	e[++cnt].nxt=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
bl bfs(){
	memset(dep,0,sizeof(dep));
	queue<ll> q;
	q.push(s);
	dep[s]=1;
	while(!q.empty()){
		ll u=q.front();
		q.pop();
		go(u){
			ll v=e[i].to;
			if(e[i].w&&!dep[v]){
				dep[v]=dep[u]+1;
				q.push(v);
			} 
		}
	}
	return dep[t];
}
ll dfs(ll u,ll flow){
	if(u==t||(!flow)) return flow;
	ll k,res=0;
	go(u){
		ll v=e[i].to;
		if(e[i].w&&dep[v]==dep[u]+1){
			k=dfs(v,min(flow,e[i].w));
			if(!k) dep[v]=maxinf;
			e[i].w-=k;
			e[i^1].w+=k;
			res+=k;
			flow-=k;
		}
	}
	return res;
}
int main(){
	m=read();
	n=read();
	x=read();
	y=read();
	s=0;
	t=n+1;
	fr(i,1,m){
		add(s,i,1);
		add(i,s,0);
	}
	fr(i,m+1,n){
		add(i,t,1);
		add(t,i,0);
	}
	while(x!=-1||y!=-1){
		add(x,y,1);
		add(y,x,0);
		x=read();
		y=read();
	}
	while(bfs()) ans+=dfs(s,maxinf);
	writeln(ans);
	for(ll i=2;i<=cnt;i+=2){
		if(e[i^1].w&&e[i^1].to!=s&&e[i].to!=t){
			writesp(e[i^1].to);
			writeln(e[i].to);
		}
	}
}

P2761

留坑

P4016

留坑

P3358

留坑

P4014

Link

(打完最大流裸题就来打费用流裸题的蒟蒻LZY是屑)

裸费用流(也可以用二分图最佳完美匹配来做)

题目大意:有\(n\)个人,\(n\)项工作,每个人都对应一项不同的工作,每个人做对应的工作都有一个对应的效益,求效益的最大/最小值。

显然要用到费用流。既然要用网络流做,那么常规地就把源点\(s\)和每个人(\(1\)$n$)连起来,因为每个人只能参加$1$项工作,所以就设最大容量为$1$,花费为$0$。再把每个人和每个工作($n$+$1$\(2n\))连起来,最大容量为\(1\),因为每个人做工作都有一个效率,所以边的花费就为对应的人做对应的工作的效益。最后把每项工作与汇点\(t\)连起来,最大流量为\(1\),花费为\(0\),跑一遍最小费用最大流和最大费用最小流就行了。(图中间很乱,凑合着看吧)

当然,我在求最大费用最大流的时候用了一些小技巧(至少不会使代码那么长QwQ),就是在求完最小费用后重新建一次边,但是把边原来的花费都变为它的相反数,这样原来较大的就变为较小的了。然后再用新的图跑一次最小费用最大流,输出答案的相反数就可以啦。

代码:

#include<bits/stdc++.h>
using namespace std;
using namespace my_std;
ll n,c[101][101],s,t,head[100001],cnt=1,dist[100001],ansc=0;
bl ck[100001];
struct node{
	ll nxt,to,flow,cost;
}e[200002];
void add(ll x,ll y,ll f,ll c){
	e[++cnt].nxt=head[x];
    e[cnt].to=y;
	e[cnt].flow=f;
	e[cnt].cost=c;
	head[x]=cnt;
}
bool spfa(){
    memset(ck,0,sizeof(ck));
    fr(i,0,2*n+1) dist[i]=maxinf;
	dist[t]=0;
	ck[t]=1;
    deque<ll> q;
	q.push_back(t);
    while(!q.empty()){
        ll u=q.front();
		q.pop_front();
        go(u){
        	ll v=e[i].to;
        	if(e[i^1].flow&&dist[v]>dist[u]-e[i].cost){
            	dist[v]=dist[u]-e[i].cost;
            	if(!ck[v]){
            	    ck[v]=1;
           	    	if(!q.empty()&&dist[q.front()]>dist[v]) q.push_front(v);
					else q.push_back(v);
            	}
        	}
        }
        ck[u]=0;
    }
    return dist[s]<maxinf;
}
ll dfs(ll u,ll sum){
    if(u==t){
		ck[t]=1;
		return sum;
	}
	ck[u]=1;
    ll k,res=0;
	go(u){
		ll v=e[i].to;
		if(!ck[v]&&e[i].flow&&(dist[u]-e[i].cost)==dist[v]){
        	k=dfs(e[i].to,min(sum-res,e[i].flow));
        	if(k){
        		ansc+=k*e[i].cost;
				res+=k;
				e[i].flow-=k;
				e[i^1].flow+=k;
        	}
        	if(res==sum) break;
    	}
	}
    return res;
}
void zkw(){
    while(spfa()){
        ck[t]=1;
        while(ck[t]){
            memset(ck,0,sizeof(ck));
            dfs(s,maxinf);
        }
    }
    return;
}
void init(){
	memset(e,0,sizeof(e));
	ansc=0;
	cnt=1;
	memset(ck,0,sizeof(ck));
	memset(head,0,sizeof(head));
}
int main(){
	n=read();
	s=0;
	t=2*n+1;
	fr(i,1,n){
		add(s,i,1,0);
		add(i,s,0,0);
	}
	fr(i,n+1,2*n){
		add(i,t,1,0);
		add(t,i,0,0);
	}
	fr(i,1,n){
		fr(j,1,n){
			c[i][j]=read();
			add(i,j+n,1,c[i][j]);
			add(j+n,i,0,-c[i][j]);
		}
	}
	zkw();
	writeln(ansc);
	init();
	fr(i,1,n){
		add(s,i,1,0);
		add(i,s,0,0);
	}
	fr(i,n+1,2*n){
		add(i,t,1,0);
		add(t,i,0,0);
	}
	fr(i,1,n){
		fr(j,1,n){
			add(i,j+n,1,-c[i][j]);
			add(j+n,i,0,c[i][j]);
		}
	}
	zkw();
	write(-ansc);
}

P2774

留坑

P4009

Link

(突然感觉做网络流题都是套个模板然后重新建边就可以了)

题目大意:一个\(n \times n\)的方格,要从\((1,1)\)开车到\((n,n)\),每走一格都会耗一格油。车辆原来有\(k\)格油,然后一些位置会有加油站,到达这个位置就要花费\(A\)的价格加至\(k\)格油,如果没加油站也可以花\(C\)价格建一个(不包括加油费用)。如果往回走也要花\(B\)价格。

看到费用,那么肯定就是费用流了。很容易想到把每一格与相邻的格子连边,但是却很难考虑油量这个变量,我们没办法去求经过一段路程(甚至往回走)并且加过几次油后的油量。所以我们得思考另一种建图方式。既然不能求油量,不如直接把油量记录到图里面去,所以整个图就被分成了\(0\)~\(k\)层,其中第\(i\)层表示目前的油量。因为我们只有一辆车,且路线不重复(如果重复那肯定不是最优的),所以就默认每条边的最大容量为\(1\)

一开始出发的油量肯定是\(k\),所以将源点\(s\)与第\(k\)层的\((1,1)\)连边,费用为\(0\),因为题目只要求到达\((n,n)\),没有说到达时的油量限制,所以就将每一层的\((n,n)\)都与汇点\(t\)连边,费用也为\(0\)。接下来我们考虑加油的情况。第一种就是所在的这个点\((x,y)\)有一个加油站且油不是满的,因为是强制性消费,所以直接将这个点连向第\(k\)层同一位置的点,费用为\(A\)。至于第二种情况,因为费用要尽可能的小,所以就只用在油量为\(0\)的情况下与第\(k\)层同一个点连一条费用为\(A+C\)的边(注意,题目说\(C\)的价格不包括加油费用)。然后就让每个点与右边或下边相邻的点连一条费用为\(0\)的点,与上边或左边的点连一条费用为\(B\)的点。

自我感觉这建图有一点难想到,但是仔细思考就会发现还是常规的建图方法。注意:空间不要嫌大,要多开一点。

代码:

#include<bits/stdc++.h>
#define maxn 900009
using namespace std;
using namespace my_std;
ll n,k,a,b,c,s,t,head[maxn],cnt=1,dist[maxn],ans=0;
bl ck[maxn],pd[101][101];
struct node{
	ll nxt,to,flow,cost;
}e[maxn];
ll g(ll oil,ll xx,ll yy){
	return oil*n*n+(xx-1)*n+yy;
}
void add(ll x,ll y,ll f,ll c){
	e[++cnt].nxt=head[x];
    e[cnt].to=y;
	e[cnt].flow=f;
	e[cnt].cost=c;
	head[x]=cnt;
}
bool spfa(){
    memset(ck,0,sizeof(ck));
    fr(i,s,t) dist[i]=maxinf;
	dist[t]=0;
	ck[t]=1;
    deque<ll> q;
	q.push_back(t);
    while(!q.empty()){
        ll u=q.front();
		q.pop_front();
        go(u){
        	ll v=e[i].to;
        	if(e[i^1].flow&&dist[v]>dist[u]-e[i].cost){
            	dist[v]=dist[u]-e[i].cost;
            	if(!ck[v]){
            	    ck[v]=1;
           	    	if(!q.empty()&&dist[v]<dist[q.front()]) q.push_front(v);
					else q.push_back(v);
            	}
        	}
        }
        ck[u]=0;
    }
    return dist[s]<maxinf;
}
ll dfs(ll u,ll sum){
    if(u==t){
		ck[t]=1;
		return sum;
	}
	ck[u]=1;
    ll k,res=0;
	go(u){
		ll v=e[i].to;
		if(!ck[v]&&e[i].flow&&(dist[u]-e[i].cost)==dist[v]){
        	k=dfs(e[i].to,min(sum-res,e[i].flow));
        	if(k){
        		ans+=k*e[i].cost;
				res+=k;
				e[i].flow-=k;
				e[i^1].flow+=k;
        	}
        	if(res==sum) break;
    	}
	}
    return res;
}
void zkw(){
    while(spfa()){
        ck[t]=1;
        while(ck[t]){
            memset(ck,0,sizeof(ck));
            dfs(s,maxinf);
        }
    }
    return;
}
int main(){
	n=read();
	k=read();
	a=read();
	b=read();
	c=read();
	s=0;
	t=(k+1)*n*n+1;
	fr(i,1,n) fr(j,1,n) pd[i][j]=read();
	add(s,g(k,1,1),1,0);
	add(g(k,1,1),s,0,0);
	fr(i,0,k){
		add(g(i,n,n),t,1,0);
		add(t,g(i,n,n),0,0);
	}
	fr(oil,0,k){
		fr(x,1,n){
			fr(y,1,n){
				if(x==n&&y==n){
					add(g(oil,n,n),t,1,0);
					add(t,g(oil,n,n),0,0);
				}
				else if(pd[x][y]&&oil!=k){
					add(g(oil,x,y),g(k,x,y),1,a);
					add(g(k,x,y),g(oil,x,y),0,-a);
				}
				else if(!oil){
					add(g(oil,x,y),g(k,x,y),1,a+c);
					add(g(k,x,y),g(oil,x,y),0,-a-c);
				}
				else{
					if(x+1<=n){
						add(g(oil,x,y),g(oil-1,x+1,y),1,0);
						add(g(oil-1,x+1,y),g(oil,x,y),0,0);
					}
					if(y+1<=n){
						add(g(oil,x,y),g(oil-1,x,y+1),1,0);
						add(g(oil-1,x,y+1),g(oil,x,y),0,0);
					}
					if(x-1>0){
						add(g(oil,x,y),g(oil-1,x-1,y),1,b);
						add(g(oil-1,x-1,y),g(oil,x,y),0,-b);
					}
					if(y-1>0){
						add(g(oil,x,y),g(oil-1,x,y-1),1,b);
						add(g(oil-1,x,y-1),g(oil,x,y),0,-b);
					}
				}
			}
		}
	}
	zkw();
	write(ans);
}

P4015

留坑

P2770

留坑

P2754

留坑

P2762

留坑

P3254

Link

感觉也挺裸的最大流

自我感觉网络流难就难在建图上。就以样例为例,先上图(中间的边有点乱,懂就行了),\(1\)$4$为单位,$5$\(9\)为圆桌

因为每个单位的人都可以去每一个桌,唯一的限制就是每个单位每个桌只能去\(1\)个人,所以不难想到把每个单位和每个圆桌都连上边,然后边的最大容量为\(1\),这样就限制了只能去一个人的条件。因为每个单位只有一定的人数,所以将源点\(s\)连至每个单位,最大容量为\(r_i\),这样也就限制了每个单位的人数。同理,将每个圆桌与汇点\(t\)连边,边的容量为\(c_i\),这样也保证了每个圆桌不会超过限坐人数。于是图建好了。

回到题目,题目要求我们求出有没有方案满足条件要求,于是跑一遍最大流判断最终到\(t\)的流是否大于\(0\)就行了。如果满足,那么遍历一遍每个单位,看看与哪个圆桌连的边(从上到下)中有流再输出它就是答案了。注意,如果有哪个单位的人没有坐上圆桌,那么也不满足要求,输出\(0\)(我在这里被坑了好久……)

代码:

#include<bits/stdc++.h>
using namespace std;
using namespace my_std;
ll m,n,s,t,r[303],head[1000001],cnt=1,dep[1000001],ans=0;
bl ck;
struct node{
	ll nxt,to,w;
}e[1000001];
void add(ll u,ll v,ll w){
	e[++cnt].nxt=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
bl bfs(){
	memset(dep,0,sizeof(dep));
	queue<ll> q;
	q.push(s);
	dep[s]=1;
	while(!q.empty()){
		ll u=q.front();
		q.pop();
		go(u){
			ll v=e[i].to;
			if(e[i].w&&!dep[v]){
				dep[v]=dep[u]+1;
				q.push(v);
			} 
		}
	}
	return dep[t];
}
ll dfs(ll u,ll flow){
	if(u==t||(!flow)) return flow;
	ll k,res=0;
	go(u){
		ll v=e[i].to;
		if(e[i].w&&dep[v]==dep[u]+1){
			k=dfs(v,min(flow,e[i].w));
			if(!k) dep[v]=maxinf;
			e[i].w-=k;
			e[i^1].w+=k;
			res+=k;
			flow-=k;
		}
	}
	return res;
}
int main(){
	m=read(),n=read();
	s=0;
	t=m+n+1;
	fr(i,1,m){
		r[i]=read();
		add(s,i,r[i]);
		add(i,s,0);
	}
	fr(i,m+1,m+n){
		ll c=read();
		fr(j,1,m){
			add(j,i,1);
			add(i,j,0);
		}
		add(i,t,c);
		add(t,i,0);
	}
	while(bfs()) ans+=dfs(s,maxinf);
	ck=chs(ans,0,1);
	fr(u,1,m){
		ll tot=0;
		go(u){
			ll v=e[i].to;
			if(v!=s&&e[i^1].w) tot++;
		}
		if(tot!=r[u]) ck=1;
	}
	if(ck){
		write(0);
		return 0;
	}
	writeln(1);
	fr(u,1,m){
		ll tot=0;
		go(u){
			ll v=e[i].to;
			if(v!=s&&e[i^1].w) writesp(v-m);
		}
		enter;
	}
}

P4012

留坑

P1251

留坑

P2763

留坑

P2766

留坑

P3355

留坑

P3357

留坑

P4013

留坑

P2765

留坑

P3356

留坑

P2764

留坑

P2775

留坑

posted @ 2020-09-18 16:48  AFewSuns  阅读(161)  评论(0编辑  收藏  举报