<学习笔记> 网络流
最大流#
code
int head[N],nex[N*N*8],ver[N*N*8],edge[N*N*8],tot=1;
void add(int x,int y,int v){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=v;
ver[++tot]=x,nex[tot]=head[y],head[y]=tot,edge[tot]=0;
}
int st,ed;
queue<int> q;
int dep[N],cur[N];
int bfs(){
memset(dep,0,sizeof(int)*(ed+1));
dep[st]=1;
q.push(st);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(dep[y] || edge[i]<=0) continue;
dep[y]=dep[x]+1;
q.push(y);
}
}
if(dep[ed]) return 1;
else return 0;
}
int dfs(int x,int flow){
if(x==ed) return flow;
for(int i=cur[x];i;i=nex[i]){
cur[x]=i;
int y=ver[i];
if(dep[y]==dep[x]+1 && edge[i]>0){
int d=dfs(y,min(flow,edge[i]));
if(d>0){
edge[i]-=d;
edge[i^1]+=d;
return d;
}
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=ed;i++) cur[i]=head[i];
while(int d=dfs(st,inf)){
ans+=d;
}
}
return ans;
}
最大流最小割定理#
对于一个网络流图
问题模型:
有
组合收益#
例题 线性代数
先钦定
用解方程的方法让
例题 employ人员雇佣
最小割求带权点覆盖 & 最大独立集#
设
最小割就是带权最小点覆盖,类似不带权,带权最大独立集=
例题 bzoj3158千钧一发
离散变量模型#
例题 切糕
考虑没有
在考虑有
如何理解:与
最大权值闭合图#
即给定一张有向图,每个点都有一个权值(可以为正或负或 0),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。
我们可以先先全选正数,然后假如这个数
费用流#
每回
code
void add(int x,int y,int w,int c){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=w,cost[tot]=c;
}
queue<int> q;
int gpre[N],gpath[N];
int dist[N];
int st,ed;
int spfa(int s,int t){
memset(gpre,-1,sizeof(gpre));
memset(dist,0x7f,sizeof(dist));
q.push(s);
dist[s]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(edge[i] && dist[y]>dist[x]+cost[i]){
dist[y]=dist[x]+cost[i];
gpre[y]=x;
gpath[y]=i;
q.push(y);
}
}
}
if(gpre[t]==-1) return 0;
else return 1;
}
pair<int,int> EKF(){
int flow=0;
int ct=0;
while(spfa(st,ed)){
int mi=(1ll<<20);
for(int x=ed;x!=st;x=gpre[x]){
mi=min(mi,edge[gpath[x]]);
}
flow+=mi;
for(int x=ed;x!=st;x=gpre[x]){
ct+=cost[gpath[x]]*mi;
edge[gpath[x]]-=mi;
edge[gpath[x]^1]+=mi;
}
}
return make_pair(flow,ct);
}
上面是保证最大流的前提下最小费用,下面是保证最小费用前提下最大流(相当于可行流)(区别很小)
code
int gpre[N*2],gpath[N*2],dist[N*2];
bool flat[N*2];
queue<int> q;
int spfa(int s,int t){
memset(gpre,-1,sizeof(gpre));
memset(dist,0x7f,sizeof(dist));
q.push(s);
dist[s]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(edge[i] && dist[y]>dist[x]+cost[i]){
dist[y]=dist[x]+cost[i];
gpre[y]=x;
gpath[y]=i;
q.push(y);
}
}
}
if(gpre[t]==-1) return 0;
else return 1;
}
int n,m,k;
int cnt=(1ll<<60);
int EKF(){
int ct=0;
while(m--&&spfa(st,ed)){//---------------------------------------------<---------
int mi=1e9;
for(int x=ed;x!=st;x=gpre[x]){
mi=min(mi,edge[gpath[x]]);
}
int qwe=0;
for(int x=ed;x!=st;x=gpre[x]){
qwe+=cost[gpath[x]]*mi;
edge[gpath[x]]-=mi;
edge[gpath[x]^1]+=mi;
}
if(qwe>0) continue;
ct+=qwe;
}
return ct;
}
Dij 费用流#
考虑到
注意到费用流是存在负边权的,所以直接用
对于求
证明,每次增广会增加一些边
所以这样操作是合法的。
所以流程就是,先来一遍
code
struct Dij_EKF{
const int V=2*1e4+10;
const int E=2*1e6;
int head[V],ver[E],nex[E],edge[E],cost[E],tot=1;
inline void add(int x,int y,int w,int c){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=w,cost[tot]=c;
ver[++tot]=x,nex[tot]=head[y],head[y]=tot,edge[tot]=0,cost[tot]=-c;
}
int gpre[V],gpath[V];
int dist[V],h[V];
int st,ed;
bool flat[V];
inline int dij(int s,int t){
memset(dist,0x7f,sizeof(int)*(ed+1));
memset(flat,0,sizeof(int)*(ed+1));
gpre[t]=-1;
dist[s]=0;
priority_queue<pair<int,int>> q;
q.push(make_pair(0,s));
while(!q.empty()){
int x=q.top().second;
q.pop();
if(flat[x]) continue;
flat[x]=1;
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(edge[i] && dist[y]>dist[x]+cost[i]+h[x]-h[y]){
dist[y]=dist[x]+cost[i]+h[x]-h[y];
gpre[y]=x;
gpath[y]=i;
q.push(make_pair(-dist[y],y));
}
}
}
if(dist[t]==dist[0]) return 0;
else return 1;
}
inline void spfa(int s,int t){
memset(h,0x7f,sizeof(int)*(ed+1));
queue<int> q;
q.push(s);
flat[s]=1; h[s]=0;
while(!q.empty()){
int x=q.front();
q.pop();
flat[x]=0;
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(edge[i] && h[y]>h[x]+cost[i]){
h[y]=h[x]+cost[i];
if(!flat[y]){
q.push(y);
flat[y]=1;
}
}
}
}
}
int EKF(){
spfa(st,ed);
int ct=0;
while(dij(st,ed)){
int mn=(1ll<<50);
for(int x=ed;x!=st;x=gpre[x]) mn=min(mn,edge[gpath[x]]);
for(int i=1;i<=ed;i++) h[i]+=dist[i];
for(int x=ed;x!=st;x=gpre[x]){
ct+=cost[gpath[x]]*mn;
edge[gpath[x]]-=mn;
edge[gpath[x]^1]+=mn;
}
}
return ct;
}
}T;
有上下界的网络流#
-
无源汇有上下界可行流(也就是循环流)#
可行流算法的核心是将一个不满足流量守恒的初始流调整成满足流量守恒的流。
我们一开始钦定每条边的流量为其下界,可以注意到的是此时每个点的入流量不等于出流量,将这个流量定义初始流。
所以我们进行调整,将原图的容量全部改为
定义源点
可以发现
我们对
-
有源汇有上下界可行流#
建出图
-
有源汇有上下界最大流#
我们跑完的可行流不一定是最大的,所以我们再在
注意这里判断是否可行是看
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int inf=0x7f7f7f7f;
int st,ed;
int head[maxn],ver[maxn*maxn],nex[maxn*maxn],edge[maxn*maxn],tot=1;
void add(int x,int y,int v){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=v;
ver[++tot]=x,nex[tot]=head[y],head[y]=tot,edge[tot]=0;
}
int A[maxn];
queue<int> q;
int dep[maxn],cur[maxn];
int bfs(){
memset(dep,0,sizeof(int)*(ed+1));
dep[st]=1;
q.push(st);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(edge[i]<=0 || dep[y]) continue;
dep[y]=dep[x]+1;
q.push(y);
}
}
if(dep[ed]) return 1;
else return 0;
}
int dfs(int x,int flow){
if(x==ed) return flow;
for(int i=cur[x];i;i=nex[i]){
cur[x]=i;
int y=ver[i];
if(dep[y]==dep[x]+1 && edge[i]>0){
int d=dfs(y,min(flow,edge[i]));
if(d>0){
edge[i]-=d;
edge[i^1]+=d;
return d;
}
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=ed;i++){
cur[i]=head[i];
}
while(int d=dfs(st,inf)){
ans+=d;
}
}
return ans;
}
signed main(){
// freopen("P5192_1.in","r",stdin);
// freopen("in.in","r",stdin);
int n,m;
while(scanf("%d%d",&n,&m)==2){
memset(A,0,sizeof(A));
tot=1;
memset(head,0,sizeof(head));
int sst=n+m+1,eed=sst+1;
st=eed+1,ed=st+1;
for(int i=1;i<=m;i++){
int g;
scanf("%d",&g);
add(n+i,eed,inf-g);
A[eed]+=g;
A[n+i]-=g;
}
int sum=0;
for(int i=1;i<=n;i++){
int c,d;
scanf("%d%d",&c,&d);
add(sst,i,d);
for(int j=1;j<=c;j++){
int t,l,r;
scanf("%d%d%d",&t,&l,&r);
t++;
add(i,n+t,r-l);
A[i]-=l;
A[n+t]+=l;
}
}
add(eed,sst,inf);
int pos=tot;
for(int i=1;i<=eed;i++){
if(A[i]==0) continue;
if(A[i]<0) add(i,ed,-A[i]);
else add(st,i,A[i]);
if(A[i]>0) sum+=A[i];
}
if(Dinic()!=sum){
printf("-1\n\n");
}
else{
int ans=edge[pos];
for(int i=head[st];i;i=nex[i]){
edge[i]=edge[i^1]=0;
}
for(int i=head[ed];i;i=nex[i]){
edge[i]=edge[i^1]=0;
}
for(int i=head[sst];i;i=nex[i]){
if(ver[i]==eed){
edge[i]=edge[i^1]=0;
}
}
st=sst,ed=eed;
ans+=Dinic();
printf("%d\n\n",ans);
}
}
}
-
有源汇上下界最小流#
考虑反边的含义,反边每增加一,相当于正边减小一,所以我们给
例题清理雪道
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=2000;
const int inf=(1<<20);
int st,ed;
int head[maxn*2],nex[maxn*maxn],ver[maxn*maxn],edge[maxn*maxn],tot=1;
void add(int x,int y,int v){
ver[++tot]=y,nex[tot]=head[x],head[x]=tot,edge[tot]=v;
ver[++tot]=x,nex[tot]=head[y],head[y]=tot,edge[tot]=0;
}
int A[maxn*maxn];
queue<int> q;
int dep[maxn],cur[maxn];
int bfs(){
memset(dep,0,sizeof(int)*(max(st,ed)+1));
dep[st]=1;
q.push(st);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nex[i]){
int y=ver[i];
if(edge[i]<=0 || dep[y]) continue;
dep[y]=dep[x]+1;
q.push(y);
}
}
if(dep[ed]) return 1;
else return 0;
}
int dfs(int x,int flow){
if(x==ed) return flow;
for(int i=cur[x];i;i=nex[i]){
cur[x]=i;
int y=ver[i];
if(dep[y]==dep[x]+1 && edge[i]>0){
int d=dfs(y,min(flow,edge[i]));
if(d>0){
edge[i]-=d;
edge[i^1]+=d;
return d;
}
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()){
int mx=max(ed,st);
for(int i=1;i<=mx;++i){
cur[i]=head[i];
}
while(int d=dfs(st,inf)){
ans+=d;
}
}
return ans;
}
signed main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int m;
scanf("%d",&m);
for(int j=1;j<=m;j++){
int x;
scanf("%d",&x);
add(i,x,inf-1);
A[i]--,A[x]++;
}
}
int sst=n*2+1,eed=sst+1;
st=eed+1,ed=st+1;
for(int i=1;i<=n;i++){
add(sst,i,inf);
add(i,eed,inf);
}
add(eed,sst,inf);
int pos=tot;
int sum=0;
for(int i=1;i<=eed;i++){
if(A[i]==0) continue;
if(A[i]<0) add(i,ed,-A[i]);
else add(st,i,A[i]);
if(A[i]>0) sum+=A[i];
}
Dinic();
int ans=edge[pos];
for(int i=head[st];i;i=nex[i]){
edge[i]=edge[i^1]=0;
}
for(int i=head[ed];i;i=nex[i]){
edge[i]=edge[i^1]=0;
}
for(int i=head[sst];i;i=nex[i]){
if(ver[i]==eed){
edge[i]=edge[i^1]=0;
}
}
st=eed,ed=sst;
ans-=Dinic();
printf("%d",ans);
}
参考资料#
作者:bloss
出处:https://www.cnblogs.com/jinjiaqioi/p/17875136.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!