ybtAu「图论」第4章 费用流
A. 【例题1】订货问题
由源点向每个月份连边,容量 \(+\infin\),费用 \(d_i\);由每个月份向汇点连边,容量 \(U_i\),费用 \(0\);由每个月份向下个月连边,容量 \(+\infin\),费用 \(m\),跑最小费用最大流即可。
#include <iostream>
#include <cstring>
#define int long long
#define N 100005
int hed[N],tal[N],flw[N],wt[N],nxt[N],cnte,n,m,S,T,s;
void adde(int u,int v,int w,int c) {tal[++cnte]=v,flw[cnte]=w,wt[cnte]=c,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w,int c) {adde(u,v,w,c),adde(v,u,0,-c);}
namespace MF
{
int cur[N],dep[N],q[N],vis[N],ans;
bool spfa()
{
memset(dep,0x3f,sizeof dep),memset(vis,0,sizeof vis);
int inf=dep[0];
dep[q[1]=S]=0;
int hd=1,tl=1;
while(hd<=tl)
{
int u=q[hd++];
vis[u]=0;
for(int i=hed[u];i;i=nxt[i]) if(flw[i]&&dep[tal[i]]>dep[u]+wt[i])
{
dep[tal[i]]=dep[u]+wt[i];
if(!vis[tal[i]]) vis[tal[i]]=1,q[++tl]=tal[i];
}
}
return dep[T]<inf;
}
int dfs(int x,int fl)
{
vis[x]=1;
if(x==T||!fl) return ans+=dep[T]*fl,fl;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(flw[i]&&!vis[tal[i]]&&dep[tal[i]]==dep[x]+wt[i])
{
int d=dfs(tal[i],std::min(fl-ret,flw[i]));
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
if(ret==fl) vis[x]=0;
return ret;
}
int dinic()
{
ans=0;
while(spfa()) memcpy(cur,hed,sizeof cur),dfs(S,1e16);
return ans;
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n>>m>>s;
S=n+1,T=n+2,cnte=1;
for(int i=1,x;i<=n;i++) std::cin>>x,de(i,T,x,0);
for(int i=1,x;i<=n;i++) std::cin>>x,de(S,i,1e16,x);
for(int i=1;i<n;i++) de(i,i+1,s,m);
std::cout<<MF::dinic();
}
B. 【例题2】工作安排
由源点向每类产品连边,容量 \(C_i\),费用 \(0\);由每类产品向能制造它的的员工连边,容量 \(+\infin\),费用 \(0\);由每个员工向汇点连 \(S_i+1\) 条边,容量 \(T_{i,j}-T_{i,j-1}\),费用 \(W_{i,j}\),跑费用流即可。
由于 \(W_{i,j}<W_{i,j+1}\),当一个员工制造的商品个数较少时,对应制造了更多商品的那些边上不会有流量。
#include <iostream>
#include <cstring>
#include <queue>
#define N 2000005
#define M 505
#define int long long
int hed[M],tal[N],flw[N],wt[N],nxt[N],cnte,n,m,S,T;
bool a[M][M];
int t[M];
void adde(int u,int v,int w,int c) {tal[++cnte]=v,flw[cnte]=w,wt[cnte]=c,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w,int c) {adde(u,v,w,c),adde(v,u,0,-c);}
namespace MF
{
int cur[M],dep[M],vis[M],ans;
std::queue<int> q;
bool spfa()
{
for(int i=1;i<=n+m+2;i++) dep[i]=1e18,vis[i]=0;
int inf=1e18;
dep[S]=0;
q.push(S);
while(!q.empty())
{
int u=q.front();
vis[u]=0,q.pop();
for(int i=hed[u];i;i=nxt[i]) if(flw[i]&&dep[tal[i]]>dep[u]+wt[i])
{
dep[tal[i]]=dep[u]+wt[i];
if(!vis[tal[i]]) vis[tal[i]]=1,q.push(tal[i]);
}
}
return dep[T]<1e16;
}
int dfs(int x,int fl)
{
if(x==T||!fl) return ans+=dep[T]*fl,fl;
vis[x]=1;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(flw[i]&&!vis[tal[i]]&&dep[tal[i]]==dep[x]+wt[i])
{
int d=dfs(tal[i],std::min(fl-ret,flw[i]));
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
if(ret==fl) vis[x]=0;
return ret;
}
int dinic()
{
ans=0;
while(spfa())
{
for(int i=1;i<=n+m+2;i++) cur[i]=hed[i];
dfs(S,1e18);
}
return ans;
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>m>>n,cnte=1,S=n+m+1,T=n+m+2;
int x;
for(int i=1;i<=n;i++) std::cin>>x,de(S,i,x,0);
for(int i=1;i<=m;i++) for(int j=1;j<=n;j++)
{
std::cin>>x;
if(x) de(j,i+n,1e18,0);
}
for(int i=1;i<=m;i++)
{
int len;
std::cin>>len;
t[0]=0;
for(int j=1;j<=len;j++) std::cin>>t[j];
t[len+1]=1e18;
for(int j=1,y;j<=len+1;j++) std::cin>>y,de(i+n,T,t[j]-t[j-1],y);
}
std::cout<<MF::dinic();
}
C. 软件开发
直接做是不好做的,所以考虑拆点。
具体地,把每天拆成“白天”和“晚上”,“白天”产生未消毒的毛巾,“晚上”处理未消毒的毛巾。
由于无论怎样,处理和购买的毛巾的总和不变,产生未消毒的毛巾的总量也不变。
知道了这一点,可以令源点源源不断地产生未消毒的毛巾,汇点接受未消毒的毛巾。
由源点向每个晚上连边,容量 \(n_i\),费用 \(0\),表示这一天产生了 \(n_i\) 条未消毒的毛巾;
由每个白天向汇点连边,容量 \(n_i\),费用 \(0\),含义同上;
由第 \(i\) 个晚上向第 \(i+a+1\) 个白天连边,容量 \(+\infin\),费用 \(f_a\),表示第 \(i\) 个晚上处理的毛巾第 \(i+a+1\) 个白天能用,\(b\) 类消毒同理;
由每个白天向第二天白天连边,容量 \(+\infin\),费用 \(0\),表示这天收到的毛巾可以留到以后再用;
由每个晚上向第二天晚上连边,容量 \(+\infin\),费用 \(0\),表示这天收到的未消毒的毛巾可以留到以后再消毒;
由源点向每个白天连边,容量 \(+\infin\),费用 \(f\),表示可以购买毛巾。
连的边看起来有点多。
#include <iostream>
#include <cstring>
#define int long long
#define N 100005
int hed[N],tal[N],flw[N],wt[N],nxt[N],cnte,n,a,b,f,fa,fb,S,T;
void adde(int u,int v,int w,int c) {tal[++cnte]=v,flw[cnte]=w,wt[cnte]=c,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w,int c) {adde(u,v,w,c),adde(v,u,0,-c);}
namespace MF
{
int cur[N],dep[N],q[N],vis[N],ans;
bool spfa()
{
memset(dep,0x3f,sizeof dep),memset(vis,0,sizeof vis);
int inf=dep[0];
dep[q[1]=S]=0;
int hd=1,tl=1;
while(hd<=tl)
{
int u=q[hd++];
vis[u]=0;
for(int i=hed[u];i;i=nxt[i]) if(flw[i]&&dep[tal[i]]>dep[u]+wt[i])
{
dep[tal[i]]=dep[u]+wt[i];
if(!vis[tal[i]]) vis[tal[i]]=1,q[++tl]=tal[i];
}
}
return dep[T]<inf;
}
int dfs(int x,int fl)
{
if(x==T||!fl) return ans+=dep[T]*fl,fl;
vis[x]=1;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(flw[i]&&!vis[tal[i]]&&dep[tal[i]]==dep[x]+wt[i])
{
int d=dfs(tal[i],std::min(fl-ret,flw[i]));
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
if(ret==fl) vis[x]=0;
return ret;
}
int dinic()
{
ans=0;
while(spfa()) memcpy(cur,hed,sizeof cur),dfs(S,1e16);
return ans;
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0),cnte=1;
std::cin>>n>>a>>b>>f>>fa>>fb;
S=n*2+1,T=n*2+2;
for(int i=1,x;i<=n;i++)
{
std::cin>>x,de(S,i+n,x,0),de(i,T,x,0),de(S,i,1e16,f);
if(i+a+1<=n) de(i+n,i+a+1,1e16,fa);
if(i+b+1<=n) de(i+n,i+b+1,1e16,fb);
if(i+1<=n) de(i,i+1,1e16,0),de(i+n,i+n+1,1e16,0);
}
std::cout<<MF::dinic();
}
D. 放置棋子
一种非常巧妙的建图方法,巧妙到 neatisaac 已经不记得了。
考虑把棋盘放满然后拿走棋子。
由于第二条限制难以直接处理,所以直接枚举每一行最多放的棋子个数,求出一种方案之后再判断是否合法即可。
设当前枚举到每一行最多放 \(x\) 个棋子。
由源点向每一行连边,容量为该行的空格数,费用 \(0\),表示这一行的棋子个数;
由每一列向汇点连边,容量为该列的空格数,费用 \(0\),表示这一列的棋子个数;
由每行向每列连边,容量 \(x\),费用 \(0\),表示该行列最多放下的棋子个数;
对每个空格,由所在行向所在列连边,容量 \(1\),费用 \(1\),表示拿走这枚棋子。
于是,我们建立了这样一个网络:流量表示处理的棋子个数(决定一个棋子是拿走还是放下),费用表示拿走的棋子个数,跑最小费用最大流,如果不能满流,那么一定是不合法的,因为有棋子没有被处理到。经过同行列连边的流量表示在这放下棋子,经过有费用的边的流量表示拿走棋子,得到的最小费用就是拿走的棋子个数。
最后判断放下的棋子个数是否合法并更新最大值即可。
#include <iostream>
#include <cstring>
#define int long long
#define N 100005
int hed[N],tal[N],flw[N],wt[N],nxt[N],cnte,n,S,T,A,B,cc,cs,an;
std::string mp[105];
void adde(int u,int v,int w,int c) {tal[++cnte]=v,flw[cnte]=w,wt[cnte]=c,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w,int c) {adde(u,v,w,c),adde(v,u,0,-c);}
namespace MF
{
int cur[N],dep[N],q[N],vis[N],ans,sna;
bool spfa()
{
memset(dep,0x3f,sizeof dep),memset(vis,0,sizeof vis);
int inf=dep[0];
dep[q[1]=S]=0;
int hd=1,tl=1;
while(hd<=tl)
{
int u=q[hd++];
vis[u]=0;
for(int i=hed[u];i;i=nxt[i]) if(flw[i]&&dep[tal[i]]>dep[u]+wt[i])
{
dep[tal[i]]=dep[u]+wt[i];
if(!vis[tal[i]]) vis[tal[i]]=1,q[++tl]=tal[i];
}
}
return dep[T]<inf;
}
int dfs(int x,int fl)
{
if(x==T||!fl) return ans+=dep[T]*fl,fl;
vis[x]=1;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(flw[i]&&!vis[tal[i]]&&dep[tal[i]]==dep[x]+wt[i])
{
int d=dfs(tal[i],std::min(fl-ret,flw[i]));
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
if(ret==fl) vis[x]=0;
return ret;
}
int dinic()
{
ans=sna=0;
while(spfa()) memcpy(cur,hed,sizeof cur),sna+=dfs(S,1e16);
return ans;
}
};
void check(int x)
{
memset(hed,0,sizeof hed),memset(nxt,0,sizeof nxt);
cnte=1;
for(int i=1;i<=n;i++)
{
de(i,i+n,x,0);
int tc=0;
for(int j=1;j<=n;j++) if(mp[i][j-1]!='/') tc++;
de(S,i,tc,0);
tc=0;
for(int j=1;j<=n;j++) if(mp[j][i-1]!='/') tc++;
de(i+n,T,tc,0);
}
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(mp[i][j-1]=='.') de(i,j+n,1,1);
int d=MF::dinic();
if(MF::sna!=cs) return;
if(x*B<=(cs-d)*A) an=std::max(an,cs-d-cc);
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
for(int case1=1;;case1++)
{
std::cin>>n>>A>>B;
if(!n) break;
cc=0,cs=0,S=2*n+1,T=2*n+2;
for(int i=1;i<=n;i++)
{
std::cin>>mp[i];
for(int j=1;j<=n;j++)
{
if(mp[i][j-1]=='C') cc++;
if(mp[i][j-1]!='/') cs++;
}
}
an=-1;
for(int i=0;i<=n;i++) check(i);
std::cout<<"Case "<<case1<<": ";
if(an==-1) std::cout<<"impossible\n";
else std::cout<<an<<'\n';
}
}
E. 订单处理
考虑一个处理过的订单对该机器的影响。
简单推导可知,令第 \(j\) 台机器处理 \(S\) 个任务,则第 \(i\) 个订单在第 \(j\) 台机器上第 \(k\) 个处理会使该机器的总时间增加 \((S-k+1)Z_{i,j}\)。
因此,由源点向每个订单连边,容量 \(1\),费用 \(0\);把每台机器拆成 \(n\) 个点,每个点对应第 \(S-k+1\) 个处理的订单,向汇点连边,容量 \(1\),费用 \(0\);由每个订单向每台机器的 \(n\) 个点连边,容量 \(1\),费用 \(kZ_{i,j}\),跑费用流即可。
#include <iostream>
#include <cstring>
#define int long long
#define N 5005
#define M 2000005
int hed[N],tal[M],flw[M],wt[M],nxt[M],cnte,n,m,S,T,a[105][105],id[105][105];
void adde(int u,int v,int w,int c) {tal[++cnte]=v,flw[cnte]=w,wt[cnte]=c,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w,int c) {adde(u,v,w,c),adde(v,u,0,-c);}
namespace MF
{
int cur[N],dep[N],q[M],ans,vis[N];
bool spfa()
{
memset(dep,0x3f,sizeof dep),memset(vis,0,sizeof vis);
int inf=dep[0];
dep[q[1]=S]=0;
int hd=1,tl=1;
while(hd<=tl)
{
int u=q[hd++];
vis[u]=0;
for(int i=hed[u];i;i=nxt[i]) if(flw[i]&&dep[tal[i]]>dep[u]+wt[i])
{
dep[tal[i]]=dep[u]+wt[i];
if(!vis[tal[i]]) vis[tal[i]]=1,q[++tl]=tal[i];
}
}
return dep[T]<inf;
}
int dfs(int x,int fl)
{
if(x==T||!fl) return ans+=dep[T]*fl,fl;
vis[x]=1;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(!vis[tal[i]]&&flw[i]&&dep[tal[i]]==dep[x]+wt[i])
{
int d=dfs(tal[i],std::min(fl-ret,flw[i]));
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
if(ret==fl) vis[x]=0;
return ret;
}
int dinic()
{
ans=0;
while(spfa()) memcpy(cur,hed,sizeof cur),dfs(S,1e16);
return ans;
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
int Tt;
std::cin>>Tt;
while(Tt--)
{
cnte=1,memset(hed,0,sizeof hed),memset(nxt,0,sizeof nxt);
std::cin>>n>>m;
int cn=n+2;
S=n+1,T=n+2;
for(int i=1;i<=n;i++) de(S,i,1,0);
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) std::cin>>a[i][j],de(id[j][i]=++cn,T,1,0);
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
for(int k=1;k<=n;k++) de(i,id[j][k],1,k*a[i][j]);
printf("%.6lf\n",MF::dinic()*1.0/n);
}
}
F. 数字配对
令 \(a_i=\prod p^k\),发现两个数 \(x\)、\(y\) 能配对,当且仅当 \(\sum k_i=\sum k_j+1\) 且 \(y\mid x\) 或相反。于是可以根据 \(\sum k\) 的奇偶性把图变成二分图。
由源点向左部点连边,容量 \(b_i\),费用 \(0\);由右部点向汇点连边,容量 \(b_i\),费用 \(0\);由左部点向能配对的右部点连边,容量 \(+\infin\),费用 \(c_ic_j\)。最大费用最大流即答案。
注意是最大费用,所以增广的方式有所变化,细节见代码。
#include <iostream>
#include <cstring>
#define int long long
#define N 5005
#define M 2000005
int hed[N],tal[M],flw[M],wt[M],nxt[M],cnte,n,S,T,a[N],b[N],c[N],f[N],ce[M];
void adde(int u,int v,int w,int c) {tal[++cnte]=v,ce[cnte]=u,flw[cnte]=w,wt[cnte]=c,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w,int c) {adde(u,v,w,c),adde(v,u,0,-c);}
namespace MF
{
int cur[N],dep[N],q[M],ans,vis[N],in[N],sum;
bool spfa()
{
memset(dep,-0x3f,sizeof dep),memset(vis,0,sizeof vis);
int inf=dep[0];
dep[q[1]=S]=0;
int hd=1,tl=1;
while(hd<=tl)
{
int u=q[hd++];
vis[u]=0;
for(int i=hed[u];i;i=nxt[i]) if(flw[i]&&dep[tal[i]]<dep[u]+wt[i])
{
dep[tal[i]]=dep[u]+wt[i],in[tal[i]]=i;
if(!vis[tal[i]]) vis[tal[i]]=1,q[++tl]=tal[i];
}
}
return dep[T]>inf;
}
bool dfs()
{
int fl=1e16;
for(int t=in[T];t;t=in[ce[t]]) fl=std::min(fl,flw[t]);
if(sum+fl*dep[T]>=0)
{
sum+=fl*dep[T],ans+=fl;
for(int t=in[T];t;t=in[ce[t]]) flw[t]-=fl,flw[t^1]+=fl;
return 1;
}
else return ans+=sum/(-dep[T]),0;
}
int dinic()
{
ans=sum=0;
while(spfa()&&dfs());
return ans;
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n,cnte=1;
S=n+1,T=n+2;
for(int i=1,x;i<=n;i++)
{
std::cin>>x,a[i]=x;
for(int j=2;x>1;j++) while(x%j==0) x/=j,f[i]++;
}
for(int i=1;i<=n;i++) std::cin>>b[i];
for(int i=1;i<=n;i++) std::cin>>c[i];
for(int i=1;i<=n;i++) (f[i]&1)?de(S,i,b[i],0):de(i,T,b[i],0);
for(int i=1;i<=n;i++) if(f[i]&1) for(int j=1;j<=n;j++)
if(abs(f[i]-f[j])==1&&(a[i]%a[j]==0||a[j]%a[i]==0)) de(i,j,1e16,c[i]*c[j]);
std::cout<<MF::dinic();
}

浙公网安备 33010602011771号