目录
1最大流最小割
- 基本概念
1) - 如何求最大流
https://www.luogu.org/problemnew/show/P3376
dinic 算法
主要思路
- dfs
int dfs(int u,int dist)// 目前流量
{
if(u==t||!dist) return dist;// 到达汇点或者已经没有流量
int res=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;int w=edge[i].w;
if(w<0||dis[v]!=dis[u]+1) continue;// 当前边权为负或不是u的下一层直接continue
int di=dfs(v,min(dist,w));
dist-=di;
res+=di;
edge[i].w-=di;
edge[i^1].w+=di;//建反边
if(dist<0) break;
}
if(res<0) dis[u]=-1;
return res;
}
- dinic
void dinic()
{
while(bfs()) ans+=dfs(s,inf);// 有增广路就继续。
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
char ch=' ';int f=1;int x=0;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=10100;
const int M=100100;
int n,m,s,t;
struct node
{
int v,nxt,w;
}edge[M<<1];
int head[N],cnt=0;
void add(int u,int v,int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
int ans=0;
queue <int> q;
int dis[N];
bool bfs()
{
while(!q.empty()) q.pop();
memset(dis,inf,sizeof(dis));
dis[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;int w=edge[i].w;
if(w>0&&dis[v]==inf)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[t]==inf) return false;
else return true;
}
int dfs(int u,int dist)
{
if(u==t||!dist) return dist;
int res=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;int w=edge[i].w;
if(w<0||dis[v]!=dis[u]+1) continue;
int di=dfs(v,min(dist,w));
dist-=di;
res+=di;
edge[i].w-=di;
edge[i^1].w+=di;
if(dist<0) break;
}
if(res<0) dis[u]=-1;
return res;
}
void dinic()
{
while(bfs()) ans+=dfs(s,inf);
}
int main()
{
n=read();m=read();s=read();t=read();
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,0);
}
dinic();
cout<<ans<<endl;
return 0;
}
- 最大流最小割定理
例题
Description
农夫JOHN为牛们做了很好的食品,但是牛吃饭很挑食. 每一头牛只喜欢吃一些食品和饮料而别的一概不吃.虽然他不一定能把所有牛喂饱,他还是想让尽可能多的牛吃到他们喜欢的食品和饮料. 农夫JOHN做了F (1 <= F <= 100) 种食品并准备了D (1 <= D <= 100) 种饮料. 他的N (1 <= N <= 100)头牛都以决定了是否愿意吃某种食物和喝某种饮料. 农夫JOHN想给每一头牛一种食品和一种饮料,使得尽可能多的牛得到喜欢的食物和饮料. 每一件食物和饮料只能由一头牛来用. 例如如果食物2被一头牛吃掉了,没有别的牛能吃食物2.
Input
-
第一行: 三个数: N, F, 和 D
-
第2…N+1行: 每一行由两个数开始F_i 和 D_i, 分别是第i 头牛可以吃的食品数和可以喝的饮料数.下F_i个整数是第i头牛可以吃的食品号,再下面的D_i个整数是第i头牛可以喝的饮料号码.
Output
- 第一行: 一个整数,最多可以喂饱的牛数.
Sample Input
4 3 3
2 2 1 2 3 1
2 2 2 3 1 2
2 2 1 3 1 2
2 1 1 3 3
输入解释:
牛 1: 食品从 {1,2}, 饮料从 {1,2} 中选
牛 2: 食品从 {2,3}, 饮料从 {1,2} 中选
牛 3: 食品从 {1,3}, 饮料从 {1,2} 中选
牛 4: 食品从 {1,3}, 饮料从 {3} 中选
Sample Output
3
输出解释:
一个方案是:
Cow 1: 不吃
Cow 2: 食品 #2, 饮料 #2
Cow 3: 食品 #1, 饮料 #1
Cow 4: 食品 #3, 饮料 #3
思路
- 最朴素的想法是源点连饮料,饮料连牛,牛再连食物,但这样有可能出现一头牛吃喝很多东西
- 所以把一头牛拆成两个点,流量为一
- 跑dinic
code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
char ch=' ';int f=1;int x=0;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=1e5+100;
struct node
{
int v,nxt,w;
}edge[N<<1];
int head[N],cnt=0;
void add(int u,int v,int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
int s,t,ans=0;
queue <int> q;
int dis[N];
bool bfs()
{
memset(dis,inf,sizeof(dis));
while(!q.empty()) q.pop();
dis[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(w>0&&dis[v]==inf)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[t]==inf) return false;
else return true;
}
int dfs(int u,int dist)
{
if(u==t||!dist) return dist;
int res=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(dis[v]!=dis[u]+1||w<0) continue;
int di=dfs(v,min(dist,w));
dist-=di;
res+=di;
edge[i].w-=di;
edge[i^1].w+=di;
if(!dist) break;
}
if(!res) dis[u]=-1;
return res;
}
void dinic()
{
while(bfs()) ans+=dfs(s,inf);
}
int main()
{
int n,f,d;
n=read();f=read();d=read();
s=0;t=f+d+n<<1 +1;
int i,j;
for(i=0;i<=t;i++) head[i]=-1;
for(i=1;i<=n;i++)
{
int ff,dd;ff=read();dd=read();
while(ff--){int fi=read();add(fi,f+i,1);add(f+i,fi,0);}
while(dd--){int di=read();add(f+n+i,f+(n<<1)+di,1);
add(f+(n<<1)+di,f+n+i,0);}
}
for(i=1;i<=f;i++)
{
add(s,i,1);add(i,s,0);
}
for(i=1;i<=n;i++)
{
add(f+i,f+n+i,1);add(f+n+i,f+i,0);
}
for(i=1;i<=d;i++)
{
add(f+(n<<1)+i,t,1);add(t,f+(n<<1)+i,0);
}
dinic();
cout<<ans<<endl;
return 0;
}
经典最小割模型-最大权闭合子图
- 问题描述
- solution
正权点点权之和-最大流
https://blog.csdn.net/can919/article/details/77603353
模板题
https://www.luogu.org/problemnew/show/P4174
思路
3. 客户相当于正权点,中转站相当于负权点。要得到一个客户的值,必须要把两个中转站全建好,这恰好符合最大权闭合子图。
- 所以我们S向客户连以C为权的边,客户和中转站连inf的边,中转站和T连花费即可
- 跑dinic
- 注意!!!!一定要把head赋为-1!!!!!
code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
inline int read() {
int x=0,f=1;char ch=' ';
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int N=1e5+100;
const int M=1e6+100;
int s,t,ans=0;
struct node
{
int v,nxt,w;
}edge[M];
int head[N],cnt=0;
void add(int u,int v,int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void insert(int u,int v,int w)
{
add(u,v,w);add(v,u,0);
}
queue <int> q;
int dis[N];
bool bfs()
{
while(!q.empty()) q.pop();
memset(dis,inf,sizeof(dis));
dis[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;int w=edge[i].w;
if(w>0&&dis[v]==inf)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[t]==inf) return false;
else return true;
}
int dfs(int u,int dist)
{
if(u==t||!dist) return dist;
int res=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;int w=edge[i].w;
if(w<0||dis[v]!=dis[u]+1) continue;
int di=dfs(v,min(dist,w));
dist-=di;
res+=di;
edge[i].w-=di;
edge[i^1].w+=di;
if(dist<0) break;
}
if(res<0) dis[u]=-1;
return res;
}
void dinic()
{
while(bfs()) ans-=dfs(s,inf);
}
int main()
{
int n,m;
n=read();m=read();
s=0;t=n+m+1;
for(int i=0;i<=t;i++) head[i]=-1;
for(int i=1;i<=n;i++)
{
int w=read();
insert(m+i,t,w);
}
for(int i=1;i<=m;i++)
{
int a=read(),b=read(),c=read();
ans+=c;
insert(i,a+m,inf);insert(i,b+m,inf);
insert(s,i,c);
}
dinic();
cout<<ans<<endl;
return 0;
}
二分图最小点权覆盖
从x或者y集合中选取一些点,使这些点覆盖所有的边,并且选出来的点的权值尽可能小。
建模:
原二分图中的边(u,v)替换为容量为INF的有向边(u,v),设立源点s和汇点t,将s和x集合中的点相连,容量为该点的权值;将y中的点同t相连,容量为该点的权值。在新图上求最大流,最大流量即为最小点权覆盖的权值和。
例题
solution
- 把一个点拆成两个点,A,B,原图上的边相当于Au->Bv,问题转换成二分图上最小点权覆盖问题
- 最大流
二分图最大点权独立集
在二分图中找到权值和最大的点集,使得它们之间两两没有边。其实它是最小点权覆盖的对偶问题。答案=总权值-最小点覆盖集。
例题
https://www.luogu.org/problemnew/show/P2774
solution
- 把点黑白染色,选出来的点两两不相邻,相当于黑白二分图,最大独立集
建图
然后点权和-最小割,跑最大点权独立集
code
#include<queue>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
inline int read() {
int x=0,f=1;char ch=' ';
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int maxn=110;
int a[maxn][maxn];
const int N=1e5+100;
const int M=1e6+1000;
int X[4]={1,0,-1,0};
int Y[4]={0,1,0,-1};
struct node
{
int v,nxt,w;
}edge[M];
int head[N],cnt;
int dis[N];
int s,t;
void add(int u,int v,int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void insert(int u,int v,int w)
{
add(u,v,w);add(v,u,0);
}
queue <int> q;
bool bfs()
{
memset(dis,inf,sizeof(dis));
while(!q.empty()) q.pop();
dis[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(w>0&&dis[v]==inf)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[t]==inf) return false;
else return true;
}
int dfs(int u,int dist)
{
if(u==t||!dist) return dist;int res=0;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;
int w=edge[i].w;
if(w<0||dis[v]!=dis[u]+1) continue;
int di=dfs(v,min(dist,w));
dist-=di;
res+=di;
edge[i].w-=di;
edge[i^1].w+=di;
if(dist<=0) break;
}
if(res<=0) dis[u]=-1;
return res;
}
int dinic()
{
int res=0;
while(bfs()) res+=dfs(s,inf);
return res;
}
int main()
{
int n,m;
n=read();m=read();
s=0;t=n*m+1;
int i,j;
int ans=0;
for(i=0;i<=t;i++) head[i]=-1;
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
int w=read();ans+=w;
if((i+j)%2) insert(s,(i-1)*m+j,w);
else insert((i-1)*m+j,t,w);
}
}
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
if((i+j)%2)
{
for(int k=0;k<=3;k++)
{
int x=i+X[k],y=j+Y[k];
if(x>0&&x<=n&&y>0&&y<=m)
{
insert((i-1)*m+j,(x-1)*m+y,inf);
}
}
}
}
}
ans-=dinic();
cout<<ans<<endl;
return 0;
}
费用流
https://www.luogu.org/problemnew/show/P3381
方法
- 使用类似dinic的方法,不过把bfs的时候换成spfa,每次只找一条最短路增广路进行增广,并在spfa的时候记录下本条增广路的路径。
- 正确性,首先spfa保证了最短路,直到没有增广路才停止保证了最大流
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
char ch=' ';int f=1;int x=0;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=5100;
const int M=51000;
struct node
{
int v,nxt,flow,cost;
}edge[M<<1];
int head[N],cnt=0;
void add(int u,int v,int flow,int cost)
{
edge[cnt].v=v;
edge[cnt].flow=flow;
edge[cnt].cost=cost;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void insert(int u,int v,int flow,int cost)
{
add(u,v,flow,cost);
add(v,u,0,-cost);
}
int maxflow=0,mincost=0;
queue <int> q;
bool vis[N];
int dis[N],flow[N];//dis最短路,flow记录最大流
int pre[N],last[N];// pre[i]表示i的前继节点,last[i]表示他们之间边的编号
int n,m,s,t;
bool spfa()
{
memset(dis,inf,sizeof(dis));
memset(flow,inf,sizeof(flow));
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();
q.push(s);
vis[s]=true;dis[s]=0;pre[t]=-1;
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=false;
for(int i=head[u];i!=-1;i=edge[i].nxt)// i!=-1 !!!!!
{
int v=edge[i].v;
int cost=edge[i].cost;
if(edge[i].flow>0&&dis[u]+cost<dis[v])
{
dis[v]=dis[u]+cost;
flow[v]=min(flow[u],edge[i].flow);
pre[v]=u;
last[v]=i;
if(!vis[v])
{
vis[v]=true;
q.push(v);
}
}
}
}
if(pre[t]==-1) return false;
else return true;
}
void dinic()
{
while(spfa())
{
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s)
{
edge[last[u]].flow-=flow[t];
edge[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
}
int main()
{
n=read();m=read();s=read();t=read();
int i,j;
for(i=1;i<=n;i++) head[i]=-1;
for(i=1;i<=m;i++)
{
int u=read(),v=read(),flow=read(),cost=read();
insert(u,v,flow,cost);
}
dinic();
cout<<maxflow<<" "<<mincost<<endl;
return 0;
}
例题
https://www.luogu.org/problemnew/show/P2153
- 题意
solution
- 首先源点就是1,汇点就是n,如果没有“不经过相同的点”这个条件,那么就是把点与点连上容量为1、费用为权值的边,然后跑最小费用最大流。
- 可是还要保证“不经过相同的点”,我们可以参考牛喝饮料的那道题,把一个点拆成两个点,连一条容量为1,费用为0的边。
- 注意源点和汇点的细节!!
- 注意N和M!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=1010;
const int M=100100;
inline int read(){
char ch=' ';int f=1;int x=0;
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
int s,t;
int maxflow=0,mincost=0;
struct node
{
int v,nxt,flow,cost;
}edge[M<<1];
int head[N],cnt;
void add(int u,int v,int flow,int cost)
{
edge[cnt].v=v;
edge[cnt].flow=flow;
edge[cnt].cost=cost;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void insert(int u,int v,int flow,int cost)
{
add(u,v,flow,cost);
add(v,u,0,-cost);
}
int dis[N],flow[N];
int pre[N],last[N];
bool vis[N];
queue <int> q;
bool spfa()
{
memset(dis,inf,sizeof(dis));
memset(flow,inf,sizeof(flow));
memset(vis,0,sizeof(vis));
while(!q.empty()) q.pop();
dis[s]=0;vis[s]=true;q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
vis[u]=false;
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;
if(edge[i].flow>0&&dis[u]+edge[i].cost<dis[v])
{
dis[v]=dis[u]+edge[i].cost;
flow[v]=min(flow[u],edge[i].flow);
pre[v]=u;
last[v]=i;
if(!vis[v])
{
vis[v]=true;
q.push(v);
}
}
}
}
if(dis[t]==inf) return false;
else return true;
}
void dinic()
{
while(spfa())
{
int u=t;
maxflow+=flow[t];//直接写++也可以
mincost+=flow[t]*dis[t];
while(u!=s)
{
edge[last[u]].flow-=flow[t];
edge[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
}
int main()
{
int n,m;
n=read();m=read();
s=1;t=n*2;
for(int i=1;i<=t;i++) head[i]=-1;
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
insert(u+n,v,1,w);
}
for(int i=2;i<n;i++) insert(i,i+n,1,0);
insert(1,n+1,inf,0);
insert(n,n+n,inf,0);
dinic();
cout<<maxflow<<" "<<mincost<<endl;
return 0;
}