ybtAu「图论」第3章 网络流模型及应用
A. 【例题1】任务分配
源点向每个任务连边,容量 \(a_i\),表示该任务在 \(A\) 上完成的花费;每个任务向 \(b_i\) 连边,表示在 \(B\) 上完成的花费;对于每个二元组,连双向边,容量为 \(v\),这样当 \(x\) 和 \(y\) 不在同一点集时,这条边就是一条割边。答案即最小割,也就是最大流。
#include <iostream>
#include <cstring>
#define int long long
#define N 2000005
#define M 20005
int hed[M],tal[N],flw[N],nxt[N],cnte,S,T,n,m;
void adde(int u,int v,int w) {tal[++cnte]=v,flw[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w) {adde(u,v,w),adde(v,u,0);}
namespace MF
{
int cur[M],dep[M],q[M];
bool bfs()
{
memset(dep,0,sizeof dep);
dep[q[1]=S]=1;
int hd=1,tl=1;
while(hd<=tl)
{
int x=q[hd++];
for(int i=hed[x];i;i=nxt[i]) if(!dep[tal[i]]&&flw[i]) dep[tal[i]]=dep[x]+1,q[++tl]=tal[i];
}
return dep[T];
}
int dfs(int x,int fl)
{
if(x==T||!fl) return fl;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(dep[tal[i]]==dep[x]+1&&flw[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;
}
return ret;
}
int dinic()
{
int ans=0;
while(bfs()) memcpy(cur,hed,sizeof cur),ans+=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=0,T=n+1,cnte=1;
for(int i=1,x,y;i<=n;i++) std::cin>>x>>y,de(S,i,x),de(i,T,y);
for(int i=1,x,y,v;i<=m;i++) std::cin>>x>>y>>v,adde(x,y,v),adde(y,x,v);
std::cout<<MF::dinic();
}
死因:cnte 没有初始化为 \(0\)
B. 【例题2】幸福值
书上提供了一种令人难以理解的建图方式。
题目要求幸福值最大,可以转化为最小割问题,把所有边容量求和并减去最小割就能得到答案。
令源点向某点连边表示选文,某点向汇点连边表示选理。对每个点的建边方式同上题。
相邻的两个点同时选文的情况,新开一个节点,源点向它连边,容量为幸福值;它向这两个相邻的点连边,容量 \(+\infin\);
同时选理,新开一个节点,它向汇点连边,容量为幸福值;这两个相邻的点向它连边,容量 \(+\infin\)。
为什么这样做是对的?如果两个相邻的点选科不同,那么显然,对这两个点新建的两个节点向源汇点连的边都在最小割中;如果相同,那么这两个相邻的点没选的那科对应的边在最小割中。
#include <iostream>
#include <cstring>
#define int long long
#define M 50005
#define N 2000005
int hed[M],tal[N],flw[N],nxt[N],cnte,S,T,n,m;
void adde(int u,int v,int w) {tal[++cnte]=v,flw[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w) {adde(u,v,w),adde(v,u,0);}
int id[105][105];
namespace MF
{
int cur[M],dep[M],q[M];
bool bfs()
{
memset(dep,0,sizeof dep);
dep[q[1]=S]=1;
int hd=1,tl=1;
while(hd<=tl)
{
int x=q[hd++];
for(int i=hed[x];i;i=nxt[i]) if(!dep[tal[i]]&&flw[i]) dep[tal[i]]=dep[x]+1,q[++tl]=tal[i];
}
return dep[T];
}
int dfs(int x,int fl)
{
if(x==T||!fl) return fl;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(dep[tal[i]]==dep[x]+1&&flw[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;
}
return ret;
}
int dinic()
{
int ans=0;
while(bfs()) memcpy(cur,hed,sizeof cur),ans+=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>>m;
int cn=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) id[i][j]=++cn;
S=cn+1,T=cn+2,cn+=2;
int x,sum=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) std::cin>>x,de(S,id[i][j],x),sum+=x;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) std::cin>>x,de(id[i][j],T,x),sum+=x;
for(int i=1;i<n;i++) for(int j=1;j<=m;j++) std::cin>>x,de(S,++cn,x),de(cn,id[i][j],1e16),de(cn,id[i+1][j],1e16),sum+=x;
for(int i=1;i<n;i++) for(int j=1;j<=m;j++) std::cin>>x,de(++cn,T,x),de(id[i][j],cn,1e16),de(id[i+1][j],cn,1e16),sum+=x;
for(int i=1;i<=n;i++) for(int j=1;j<m;j++) std::cin>>x,de(S,++cn,x),de(cn,id[i][j],1e16),de(cn,id[i][j+1],1e16),sum+=x;
for(int i=1;i<=n;i++) for(int j=1;j<m;j++) std::cin>>x,de(++cn,T,x),de(id[i][j],cn,1e16),de(id[i][j+1],cn,1e16),sum+=x;
std::cout<<sum-MF::dinic();
}
C. 【例题3】飞行计划
由源点向每个实验连边,容量 \(p_i\);由每个实验向仪器连边,容量 \(+\infin\);由每个仪器向汇点连边,容量 \(c_i\)。\(\sum p\) 减最小割就是最大净收益,最后一次 \(BFS\) 能到达的实验和仪器节点就是答案。
为什么这一题只对 \(p_i\) 求和?如果一个实验被进行,那么它与汇点在同一集合内,与源点的连边被加入最小割,\(p_i\) 加入答案,由于实验和仪器连边容量 \(+\infin\),它需要的所有仪器都会被选择,这些仪器与汇点的连边不在最小割内,\(c_i\) 不加入答案。
#include <iostream>
#include <sstream>
#include <cstring>
#define int long long
#define N 1000005
#define M 105
int hed[M],tal[N],flw[N],nxt[N],cnte,S,T,n,m;
void adde(int u,int v,int w) {tal[++cnte]=v,flw[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w) {adde(u,v,w),adde(v,u,0);}
namespace MF
{
int cur[M],dep[M],q[M],ans,fg;
bool bfs()
{
memset(dep,0,sizeof dep);
dep[q[1]=S]=1;
int hd=1,tl=1;
while(hd<=tl)
{
int x=q[hd++];
for(int i=hed[x];i;i=nxt[i]) if(!dep[tal[i]]&&flw[i]) dep[tal[i]]=dep[x]+1,q[++tl]=tal[i];
}
return dep[T];
}
int dfs(int x,int fl)
{
if(x==T)
{
ans+=fl,fg=1;
return fl;
}
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(dep[tal[i]]==dep[x]+1&&flw[i])
{
int d=dfs(tal[i],std::min(fl-ret,flw[i]));
if(!d) continue;
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
return ret;
}
int dinic()
{
ans=0;
while(bfs())
{
memcpy(cur,hed,sizeof cur);
fg=1;
while(fg) fg=0,dfs(S,1e16);
}
return ans;
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>m>>n,S=m+n+2,T=m+n+1,cnte=1;
std::string tt1;
std::getline(std::cin,tt1);
int sum=0;
for(int i=1,x,t;i<=m;i++)
{
std::string s;
std::getline(std::cin,s);
std::istringstream iss(s);
iss>>x,de(S,i,x),sum+=x;
while(iss>>t) de(i,t+m,1e16);
}
for(int i=1,x;i<=n;i++) std::cin>>x,de(i+m,T,x);
int ans=sum-MF::dinic();
for(int i=1;i<=m;i++) if(MF::dep[i]) std::cout<<i<<' ';
std::cout<<'\n';
for(int i=1;i<=n;i++) if(MF::dep[i+m]) std::cout<<i<<' ';
std::cout<<'\n'<<ans;
}
D. 【例题4】唯一切边
判断最小割的唯一性。
如果最小割是唯一的,那也就意味着源点与汇点的点集是唯一确定的。
由于最小割的边都满流了,那么那些没满流的边所连接的两个节点一定在同一集合内。于是从源点和汇点开始搜,如果没有搜不到的节点,那么最小割是唯一的;否则,如果有搜不到的节点,那么说明它连接的所有边都满流了,所以既可以切掉指向它的边,又可以切掉它向外指的边,最小割就不唯一了。
#include <iostream>
#include <cstring>
#define int long long
#define N 20005
#define M 1005
int hed[M],tal[N],flw[N],nxt[N],cnte,S,T,n,m,ans;
void adde(int u,int v,int w) {tal[++cnte]=v,flw[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w) {adde(u,v,w),adde(v,u,0);}
namespace MF
{
int cur[M],dep[M],q[M],vis[M];
bool bfs()
{
memset(dep,0,sizeof dep);
dep[q[1]=S]=1;
int hd=1,tl=1;
while(hd<=tl)
{
int x=q[hd++];
for(int i=hed[x];i;i=nxt[i]) if(!dep[tal[i]]&&flw[i]) dep[tal[i]]=dep[x]+1,q[++tl]=tal[i];
}
return dep[T];
}
int dfs(int x,int fl)
{
if(x==T||!fl) return fl;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(dep[tal[i]]==dep[x]+1&&flw[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;
}
return ret;
}
int dinic()
{
int ans=0;
while(bfs()) memcpy(cur,hed,sizeof cur),ans+=dfs(S,1e16);
return ans;
}
void dfs1(int x)
{
ans++,vis[x]=1;
for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]&&flw[i]) dfs1(tal[i]);
}
void dfs2(int x)
{
ans++,vis[x]=1;
for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]&&flw[i^1]) dfs2(tal[i]);
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
while(std::cin>>n>>m>>S>>T)
{
if(!n&&!m&&!S&&!T) return 0;
cnte=1;
memset(hed,0,sizeof hed),memset(tal,0,sizeof tal),memset(flw,0,sizeof flw),memset(nxt,0,sizeof nxt);
for(int i=1,u,v,w;i<=m;i++) std::cin>>u>>v>>w,adde(u,v,w),adde(v,u,w);
MF::dinic();
ans=0;
memset(MF::vis,0,sizeof MF::vis);
MF::dfs1(S),MF::dfs2(T);
if(ans==n) std::cout<<"UNIQUE\n";
else std::cout<<"AMBIGUOUS\n";
}
}
E. 矛盾指数
01 分数规划求最大密度子图。
令被砍掉的边数为 \(E\),点数为 \(V\),假设某次二分的 \(mid<\max\frac{E}{V}\),简单变换可得:
于是就可以建图跑最小割了……吗?
注意到最小割是无法把权值放到点上的,所以考虑点权转边权。
具体地,从每个点向汇点连边,容量 \(mid\);把每条边当成一个点,向两个端点连边,容量 \(+\infin\);由源点向每条边连边,容量 \(1\)。
令跟源点联通的点是删掉的,那么如果一条边被删掉了,它的两个端点也一定被删掉了;反之,如果一个点没被删掉,那么它所连的边一定不能删。
令删掉的边有 \(E_1\) 条,于是有 \(E=m-E_1\),因此用边数减去最小割检查是否大于 \(0\) 即可。
#include <iostream>
#include <cstring>
#define int long long
#define N 50005
#define M 20005
int hed[M],tal[N],nxt[N],cnte,S,T,n,m;
std::pair<int,int> e[N];
double flw[N];
void adde(int u,int v,double w) {tal[++cnte]=v,flw[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,double w) {adde(u,v,w),adde(v,u,0);}
namespace MF
{
int cur[M],dep[M],q[M];
bool bfs()
{
memset(dep,0,sizeof dep);
dep[q[1]=S]=1;
int hd=1,tl=1;
while(hd<=tl)
{
int x=q[hd++];
for(int i=hed[x];i;i=nxt[i]) if(!dep[tal[i]]&&flw[i]) dep[tal[i]]=dep[x]+1,q[++tl]=tal[i];
}
return dep[T];
}
double dfs(int x,double fl)
{
if(x==T||!fl) return fl;
double ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(dep[tal[i]]==dep[x]+1&&flw[i])
{
double d=dfs(tal[i],std::min(fl-ret,(double)flw[i]));
ret+=d,flw[i]-=d,flw[i^1]+=d;
if(ret==fl) break;
}
return ret;
}
double dinic()
{
double ans=0;
while(bfs()) memcpy(cur,hed,sizeof cur),ans+=dfs(S,1e16);
return ans;
}
};
bool check(double x)
{
//std::cout<<"check "<<x<<'\n';
memset(hed,0,sizeof hed),memset(tal,0,sizeof tal),memset(nxt,0,sizeof nxt),cnte=1;
for(int i=1;i<=n;i++) de(i,T,x);
for(int i=1;i<=m;i++) de(S,i+n,1),de(i+n,e[i].first,1e16),de(i+n,e[i].second,1e16);
return ((double)m-MF::dinic())>0;
}
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n>>m,S=n+m+1,T=n+m+2;
if(!m) return std::cout<<"1\n1",0;
for(int i=1,u,v;i<=m;i++) std::cin>>u>>v,e[i]={u,v};
double l=0,r=1e16;
while(r-l>1e-6)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
check(l);
int ans=0;
for(int i=1;i<=n;i++) if(MF::dep[i]) ans++;
std::cout<<ans<<'\n';
for(int i=1;i<=n;i++) if(MF::dep[i]) std::cout<<i<<' ';
}
F. 文理分科
跟 B 题差不多,只需要把相邻两个变成相邻四个就行了。
注意对每个点新开的点不仅要和四联通的点连边还要和中间的点连。
#include <iostream>
#include <cstring>
#define int long long
#define N 5000005
#define M 60005
int hed[M],tal[N],flw[N],nxt[N],cnte,S,T,n,m,id[105][105];
int dx[5]={-1,1,0,0,0},dy[5]={0,0,-1,1,0};
void adde(int u,int v,int w) {tal[++cnte]=v,flw[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
void de(int u,int v,int w) {adde(u,v,w),adde(v,u,0);}
namespace MF
{
int cur[M],dep[M],q[M];
bool bfs()
{
memset(dep,0,sizeof dep);
dep[q[1]=S]=1;
int hd=1,tl=1;
while(hd<=tl)
{
int x=q[hd++];
for(int i=hed[x];i;i=nxt[i]) if(!dep[tal[i]]&&flw[i]) dep[tal[i]]=dep[x]+1,q[++tl]=tal[i];
}
return dep[T];
}
int dfs(int x,int fl)
{
if(x==T||!fl) return fl;
int ret=0;
for(int &i=cur[x];i;i=nxt[i]) if(dep[tal[i]]==dep[x]+1&&flw[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;
}
return ret;
}
int dinic()
{
int ans=0;
while(bfs()) memcpy(cur,hed,sizeof cur),ans+=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,cnte=1;
int x,cn=0,sum=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) id[i][j]=++cn;
S=++cn,T=++cn;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) std::cin>>x,de(S,id[i][j],x),sum+=x;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) std::cin>>x,de(id[i][j],T,x),sum+=x;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
{
std::cin>>x,cn++,de(S,cn,x),sum+=x;
for(int k=0;k<5;k++)
{
int nx=i+dx[k],ny=j+dy[k];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m) de(cn,id[nx][ny],1e16);
}
}
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
{
std::cin>>x,cn++,de(cn,T,x),sum+=x;
for(int k=0;k<5;k++)
{
int nx=i+dx[k],ny=j+dy[k];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m) de(id[nx][ny],cn,1e16);
}
}
std::cout<<sum-MF::dinic();
}

浙公网安备 33010602011771号