网络流-最小割
一些定义:
割:
对于一个网络流图G=(V,E)
,其割的定义为一种点的划分方式:将所有的点划分为S
和T=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;
}