【瞎口胡】上下界网络流
在部分网络流问题中,会要求边的流量在一个区间 之间,即新增了一个对流量下界的限制。
这种问题被称为「上下界网络流」。
如果你不知道什么是网络流,我建议你马上去读这里。
无源汇上下界可行流#
求出一组流 使得每个点流量平衡。 点是流量平衡的,当且仅当 ,即流入流量等于流出流量。
对于第 条边 至少需要流 的流量,我们不妨将这部分流量流完,然后建立一个「残」网络,该网络中第 条边为 。
观察到,如果所有点流量平衡,那么我们就找到了一组可行流。反之,设每个点的净流入量(流入 - 流出)为 ,那么:
- 如果 ,那么该点流量平衡,我们不用处理这个点。
- 如果 ,说明流入的流量过多,我们需要从该点流出 的流量到其它点。因此,我们需要一个在残网络上的虚拟源点 ,然后从 流出 的流量到 。
- 如果 ,说明流出的流量过多,我们需要从其它点接受 的流量。因此,我们需要一个在残网络上的虚拟汇点 ,然后从该点流 的流量到 。
最后,在残网络上跑一遍 到 的最大流,满流( 流出的流量和 流入的流量相等)则有一组可行流,否则不存在可行流。
正确性显然。
无源汇上下界最小费用可行流#
把最大流改为费用流即可。
总费用即为费用流的费用加上流满每条边下界的费用。
有源汇上下界(最小费用)可行流#
区别于无源汇的上下界网络流,有源汇上下界网络流对于源汇 ,只需要满足 的净流出量等于 的净流入量。其它点仍然需要流量平衡。
一种做法是连一条 的边,然后就变为了无源汇上下界网络流。 到 的流量就是这条边流过的流量。
例题 1 [AHOI2014/JSOI2014]支线剧情#
题意
给定 个点和 条边的带权有向图,保证从 开始可以走到全部 个点。每次可以选择一条从 开始的路径,给路径上的每一条边打上一个标记,代价为路径上的边权之和。询问给每条边都打上标记的最小代价。
,边权 满足
题解
每条边至少经过一次等价于至少流过 的流量,因此边 在网络流图中表示为 。路径可以在任何点结束,于是建立超级汇点 (注意与虚拟汇点 区分,它们不是同一个点),并对于每个点 连接边 。
然后就是一个源点为 汇点为 的有源汇上下界最小费用可行流。
# include <bits/stdc++.h>
const int N=100010,MAXN=1010,INF=0x3f3f3f3f;
struct Edge{
int to,next,v,w;
};
int fsum[MAXN],coss;
struct Dinic{
Edge edge[N];
int head[MAXN],sum;
bool vis[MAXN];
int dis[MAXN],s,t,cost;
Dinic(){
sum=1;
return;
}
inline void add(int x,int y,int v,int w){
edge[++sum]=(Edge){y,head[x],v,w},head[x]=sum;
return;
}
inline void addedge(int x,int y,int v,int w){
add(x,y,v,w),add(y,x,0,-w);
return;
}
inline bool spfa(void){
std::queue <int> q;
memset(dis,INF,sizeof(dis)),dis[s]=0;
q.push(s);
while(!q.empty()){
int i=q.front();
q.pop(),vis[i]=false;
for(int j=head[i];j;j=edge[j].next){
int to=edge[j].to;
if(edge[j].v&&dis[to]>dis[i]+edge[j].w){
dis[to]=dis[i]+edge[j].w;
if(!vis[to])
q.push(to),vis[to]=true;
}
}
}
return dis[t]<INF;
}
int dfs(int i,int flow){
if(i==t)
return flow;
vis[i]=true;
int maxsum=0;
for(int j=head[i];j;j=edge[j].next){
int to=edge[j].to;
if(vis[to]||!edge[j].v||dis[to]!=dis[i]+edge[j].w)
continue;
int res=dfs(to,std::min(edge[j].v,flow));
flow-=res,maxsum+=res,edge[j].v-=res,edge[j^1].v+=res,cost+=edge[j].w*res;
if(!flow)
break;
}
vis[i]=false;
if(!maxsum)
dis[i]=INF;
return maxsum;
}
inline void solve(void){
while(spfa()){
dfs(s,INF);
}
return;
}
}G;
int n;
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int main(void){
n=read();
for(int i=1;i<=n;++i){
int d=read();
while(d--){
int to=read(),ti=read();
G.addedge(i,to,INF,ti);
++fsum[to],--fsum[i],coss+=ti;
}
}
// n+1 S'
// n+2 T'
// n+3 T
G.s=n+1,G.t=n+2;
for(int i=1;i<=n;++i)
G.addedge(i,n+3,INF,0);
G.addedge(n+3,1,INF,0); // 循环边
for(int i=1;i<=n;++i){
if(fsum[i]>0){
G.addedge(G.s,i,fsum[i],0);
}else if(fsum[i]<0){
G.addedge(i,G.t,-fsum[i],0);
}
}
G.solve();
printf("%d",coss+G.cost);
return 0;
}
有源汇上下界(最小费用)最大流#
首先跑出一组可行流。如果它不存在,那么无解。
这个时候残量网络一定是流量平衡的。我们把 和所有与它们相连的边删掉,再跑一遍 到 的(最小费用)最大流「榨干」剩下的流量即可。最后和可行流的流量相加。
当然实现的时候不删也是可以的,入度为 的 和出度为 的 显然也不会影响流量...
有源汇上下界(最小费用)最小流#
考虑反向榨干,从 往 跑最大流,然后用可行流的流量减掉。
作者:Meatherm
出处:https://www.cnblogs.com/Meatherm/p/16050733.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下