网络流-最小割

网络流-最小割

一些定义:

割:

对于一个网络流图G=(V,E),其割的定义为一种点的划分方式:将所有的点划分为ST=V-S两个集合,源点s属于S,汇点t属于T

割的容量:

指连接S中的点和T中的点的边容量的总和,符号为C(S,T)

最小割问题:

求一个割(S,T)使得割C(S,T)最小

最大流最小割定理:最小割等于最大流

证明:详情参见:OI Wiki 最大流最小割定理

最小割模型:

问题模型 1

有 n 个物品和两个集合 A,B,如果一个物品没有放入 A 集合会花费 a_i,没有放入 B 集合会花费 b_i;还有若干个形如 u_i,v_i,w_i 限制条件,表示如果 u_i 和 v_i 同时不在一个集合会花费 w_i。每个物品必须且只能属于一个集合,求最小的代价。

这是一个经典的 二者选其一 的最小割题目。我们对于每个集合设置源点 s 和汇点 t,第 i 个点由 s 连一条容量为 a_i 的边、向 t 连一条容量为 b_i 的边。对于限制条件 u,v,w,我们在 u,v 之间连容量为 w 的双向边。

注意到当源点和汇点不相连时,代表这些点都选择了其中一个集合。如果将连向 s 或 t 的边割开,表示不放在 A 或 B 集合,如果把物品之间的边割开,表示这两个物品不放在同一个集合。

问题模型 2

最大权值闭合图,即给定一张有向图,每个点都有一个权值(可以为正或负或 0),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。

做法:建立超级源点 s 和超级汇点 t,若节点 u 权值为正,则 s 向 u 连一条有向边,边权即为该点点权;若节点 u 权值为负,则由 u 向 t 连一条有向边,边权即为该点点权的相反数。原图上所有边权改为 \infty。跑网络最大流,将所有正权值之和减去最大流,即为答案。

几个小结论来证明:

1.每一个符合条件的子图都对应流量网络中的一个割。因为每一个割将网络分为两部分,与 s 相连的那部分满足没有边指向另一部分,于是满足上述条件。这个命题是充要的。
2.最小割所去除的边必须与 s 和 t 其中一者相连。因为否则边权是 \infty,不可能成为最小割。
3.我们所选择的那部分子图,权值和 = 所有正权值之和 - 我们未选择的正权值点的权值之和 + 我们选择的负权值点的权值之和。当我们不选择一个正权值点时,其与 s 的连边会被断开;当我们选择一个负权值点时,其与 t 的连边会被断开。断开的边的边权之和即为割的容量。于是上述式子转化为:权值和 = 所有正权值之和 - 割的容量。
4.于是得出结论,最大权值和 = 所有正权值之和 - 最小割 = 所有正权值之和 - 最大流。

最小割就是最小花费。


例题:4020. 【雅礼联考DAY02】Revolution

题目大意:

地图是个矩形的网格。
可以花费一定金钱在一些格子投资。
被投资的格子或者四连通的格子都被投资的话,我就可以获得该格子的收益。
利益最大化是作为商人的基本准则,但这是计算机的任务,拜托您了。

输入:

第一行两个数 n,m(n,m ≤ 20),表示矩形的长和宽。
接下来 n 行,每行是 m 个字符组成的字符串,描述投资的花费。
接下来 n 行,每行是 m 个字符组成的字符串,表示该格子的收益。
花费和收益按照一种奇葩的方式给出:
字符 数
‘0’ -’ 9’ 0-9
‘a’ -’ z’ 10-35
‘A’ -’ Z’ 36-61

输出:

一个数,表示收益的和减去投资的和的最大值。

思路:

最小割?最大流? idk:P
但是相同点:黑白染色(遇见方格建图的时候多可以进行此操作)
尝试最大流:
设最大流为答案,则对每个白点进行…… 肯定是不行的!!!( 经过了两个半小时的尝逝:( )
那么尝试最小割:
设答案=价值总和-最小割,那么每个点可以拆为三个点q1,q2,q3
对于白点从q1向q2连一条容量为cost(成本)的边,从q2向q3连一条容量为w(价值)的边
那么在最小割中,容量为cost的边的状态与容量为w的边的状态,分别对应了单独投资这个格子不投资这个格子的状态
这样建图能满足的原因是假如q1,q2,q3同时属于一个集合时,此时的割一定不是最小割,但是还有一个点四联通的时候,它的价值也是可以被计算的,那么设一个白点为i点,其上、下、左、右四个黑点分别对应a,b,c,d。那么i3可以与a1,b1,c1,d1连一条容量为inf(无穷大)的边,a1,b1,c1,d1分别与a2,b2,c2,d2连一条容量为w的边,a2,b2,c2,d2分别于a3,b3,c3,d3连一条容量为cost的边,那么此时的图初步满足了我们要求的所以性质。
但是这样操作会使得当i处于用四联通的方式去取得其价值时,黑点的w边与cost边都会被割在同一个集合中。那我们不将i3与a1,b1,c1,d1连一条容量为inf(无穷大)的边,而是将i3与a2,b2,c2,d2连一条容量为inf的边,那么此时再将i2与a3,b3,c3,d3连一条容量为inf的边,将s与i1连一条inf的边,将a3,b3,c3,d3与t连一条容量为inf的边,就可以让只选黑点格子的状态也被包含。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const long long inf=1e18;
int read(){
	char c=getchar();
	while ((c>='0'&&c<='9')==false&&(c>='a'&&c<='z')==false&&(c>='A'&&c<='Z')==false) c=getchar();
	if (c>='0'&&c<='9') return c-'0';
	else if (c>='a'&&c<='z') return c-'a'+10;
	else return c-'A'+36;
}
long long max(long long a,long long b){
	return a>b?a:b; 
}
long long min(long long a,long long b){
	return a<b?a:b;
}
int en=1,fi[1310],cur[1310];
struct rec{
	int e,nex;
	long long d;
}z[1000010];
void add(int s,int e,long long d){
	z[++en].e=e;
	z[en].d=d;
	z[en].nex=fi[s];
	fi[s]=en;
}
struct que{
	int l,r;
	int a[100010];
	void memsets(){
		l=1,r=0;
	}
	void pop(){
		l++;
	}
	int front(){
		return a[l];
	}
	void push(int x){
		a[++r]=x;
	}
	bool empty(){
		return r-l+1==0;
	}
}q;
int d[10010],cnt[10010];
int n;
void bfs(int s,int t){
	for (int i=1;i<=n;i++){
		d[i]=-1;
	}
	d[t]=0;
	cnt[0]=1;
	q.push(t);
	int x;
	while (q.empty()==false){
		x=q.front();
		q.pop();
		for (int i=fi[x];i!=0;i=z[i].nex){
			if (d[z[i].e]==-1){
				d[z[i].e]=d[x]+1;
				++cnt[d[z[i].e]];
				q.push(z[i].e);
			}
		}
	}
	return ;
}
long long max_flow;
long long dfs(int x,long long flow,int s,int t){
	if (x==t) return flow;
	long long used=0,w;
	for (int i=cur[x];i!=0;i=z[i].nex){
		cur[x]=i;
		if (z[i].d==0||d[z[i].e]+1!=d[x]) continue;
		w=dfs(z[i].e,min(flow-used,z[i].d),s,t);
		z[i].d-=w;z[i^1].d+=w;
		used+=w;
		if (used==flow) return used;
	}
	--cnt[d[x]];
	if (cnt[d[x]]==0) d[s]=n+1;
	++d[x];
	++cnt[d[x]];
	return used;
}
void ISAP(int s,int t){
	bfs(s,t);
	while (d[s]<n){
		for (int i=1;i<=n;i++) cur[i]=fi[i];
		max_flow+=dfs(s,inf,s,t);
	}
	return ;
}
int len_k,len_c;
int q1(int x,int y){
	return (x-1)*len_k+y; 
}
int q2(int x,int y){
	return (x-1)*len_k+y+len_c*len_k;
}
int q3(int x,int y){
	return (x-1)*len_k+y+len_c*len_k*2;
}
bool vis(int i,int j){
	return (i+j)%2==1;
}
void add_edge(int s,int e,long long d){
	add(s,e,d);add(e,s,0); 
}
int l[]={1,-1,0,0},r[]={0,0,1,-1};
int a_c[30][30],a_w[30][30];
int s,t;
long long ans;
void init(){
	scanf("%d%d",&len_c,&len_k);
	s=len_k*len_c*3+1;
	t=s+1;
	n=t;
	for (int i=1;i<=len_c;i++){
		for (int j=1;j<=len_k;j++){
			a_c[i][j]=read();
		}
	}
	for (int i=1;i<=len_c;i++){
		for (int j=1;j<=len_k;j++){
			a_w[i][j]=read();
			ans+=a_w[i][j];
		}
	}
	for (int i=1;i<=len_c;i++){
		for (int j=1;j<=len_k;j++){
			int z1=q1(i,j),z2=q2(i,j),z3=q3(i,j),x,y;
			if (vis(i,j)==true){//白点 
				
				add_edge(s,z1,inf);
				add_edge(z1,z2,a_c[i][j]);
				add_edge(z2,z3,a_w[i][j]);
				
				for (int k=0;k<=3;k++){
					x=i+l[k];y=j+r[k];
					if (x<1||y<1||x>len_c||y>len_k) continue;
					add_edge(z2,q1(x,y),inf);
					add_edge(z3,q2(x,y),inf);
				}
				
			}
			else{//黑点 
			
				add_edge(z1,z2,a_w[i][j]);
				add_edge(z2,z3,a_c[i][j]);
				add_edge(z3,t,inf);
			
			}
		}
	}
}
void print(){
	printf("%lld",ans-max_flow);
}
int main(){
	init();
	ISAP(s,t);
	print();
	return 0;
}

  • 25
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值