Mogician
2018-07-27 10:05:59
网络流
与网络流相关的一个概念是流.设
容量限制:
反对称性:
流守恒性:
流
而最大流就是值最大的流.
可以形象地理解为:源点是一个水库,汇点是一个用水量无限的用户,其它点是中转站,而边就是连接三者的水管.一个流就是一种合法的输水方案(每条水管运送水量在其容量之内,除了源点不会莫名其妙地出现水),它的值就是用户接受到的水量,而最大流就是用户接受水量最大的输水方案.
残留网络是由可以容纳更多流的边组成的,即如果
一条增广路则是指
以下定义:
$w[i][j]$为连接$i,j$的边的边权 $c[i]$为边$i$的费用 $dis[i]$为起点到点$i$的距离 $S$为源点 $T$为汇点
Dinic算法大致步骤:
1.用BFS给图标号,每个点的标号为到源点的最近距离(只走剩余容量大于0的边).
2.若BFS无法到达汇点则算法结束.
3.DFS一遍找出一条从源点到汇点的增广路,DFS时只走标号比当前点大1的点.
4.若没有这样的路径则返回1,否则将此路计入答案,并更新反向边,重复3.
反向边的概念:
假设有这样一个图(边上的数字代表其容量)
现在有一条2到5的路径
对于路径上每一条边(u,v),建立一条(v,u),容量为经过(u,v)的流量的边(图中绿色边)
假设又有一条6到1的边
那么最终的图是这样的(每条边上:(容量,流量))
例子:
BFS标号
DFS
更新反向边
由于无路可走,再BFS并DFS
更新反向边
无路可走,退出
最终:
PS:每条边上(容量,流量) Code(P3376)
#define out(i,a,now) for(int i=a.be[now],to=a.v[i];i;i=a.ne[i],to=a.v[i])
#define fo(i,a,b) for(i=a;i<=b;i++)
struct adj_list
{
LL be[maxn],ne[maxm<<2],v[maxm<<2],w[maxm<<2],cnt;
void init()
{
qk(be); cnt=1;
}
void add(LL x,LL y,LL z)
{
v[++cnt]=y;
ne[cnt]=be[x];
w[cnt]=z;
be[x]=cnt;
}
}v;
LL bfs()//标号
{
queue<LL> q;
q.push(t);
qk(id);
id[t]=1;
while (!q.empty())
{
LL now=q.front();
q.pop();
if (now==s) break;
out(i,v,now)
if (v.w[i^1] && !id[to])
{
id[to]=id[now]+1;
q.push(to);
}
}
return id[s];//判断是否有可行流
}
LL dfs(LL now,LL flow)//寻找可行流
{
if (now==t) return flow;
LL miflow;
out(i,v,now)
{
LL wei=v.w[i];
if (wei && id[to]==id[now]-1)
{
miflow=dfs(to,min(flow,wei));
if (miflow)
{
v.w[i]-=miflow;
v.w[i^1]+=miflow;//增加反向边流量
return miflow;
}
}
}
return 0;
}
LL dinic()
{
LL res=0;
while (bfs())
{
LL temp;
while (temp=dfs(s,LLONG_MAX>>1))
res+=temp;
}
return res;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
LL i;
LL x,y,z;
v.init();
fo(i,1,m)
{
scanf("%lld%lld%lld",&x,&y,&z);
v.add(x,y,z);
v.add(y,x,0);
}
LL ans=dinic();
printf("%lld",ans);
}
现在你需要为流经一条边
鉴于现在SPFA人人喊打,以下给出一种使用dijkstra算法的方法.
由于费用可能为负数,普通的dijkstra算法无法实现.因此我们考虑对原图做一次SPFA求出源点到每个点的最短路,之后我们为每个点
但是跑完之后需要加入反向边,原来的
所以我们只要对于每个点
由于
h[S]又是一个常量(就是
Code(P3381)
#define out(i,a,now) for(int i=a.be[now],to=a.v[i];~i;i=a.ne[i],to=a.v[i])
#define fo(i,a,b) for(i=a;i<=b;i++)
struct adj_list
{
LL be[maxn],ne[maxm<<4],v[maxm<<4],w[maxm<<4],c[maxm<<4],cnt;
void init()
{
memset(be,-1,sizeof(be));
cnt=0;
}
void add(LL pre,LL nxt,LL wei,LL cost)
{
v[cnt]=nxt;
ne[cnt]=be[pre];
be[pre]=cnt;
w[cnt]=wei;
c[cnt]=cost;
++cnt;
}
};
struct NetWork
{
adj_list v;
LL dis[maxn],h[maxn],pre_node[maxn],pre_edge[maxn],inque[maxn];
void SPFA(LL be,LL en)
{
queue<LL> q;
qk(inque);
LL i;
fo(i,1,n) h[i]=LLONG_MAX>>1;
h[be]=0; inque[be]=1;
q.push(be);
while (!q.empty())
{
LL now=q.front();
q.pop();
out(i,v,now)
if (h[now]+v.c[i]<h[to] && v.w[i])
{
h[to]=h[now]+v.c[i];
if (!inque[to])
{
q.push(to);
inque[to]=1;
}
}
inque[now]=0;
}
}
void MCMF(LL s,LL t,LL &flow,LL &cost)
{
flow=cost=0;
SPFA(s,t);//用一次SPFA更新h数组
while (1)
{
pre_node[s]=pre_edge[s]=-1;//开始堆优化dijkstra算法
LL i;
fo(i,1,n) dis[i]=LLONG_MAX>>1;
dis[s]=0;
priority_queue<Pair,vector<Pair>,greater<Pair> > pq;
pq.push(mp(0,s));
while(!pq.empty())
{
Pair now=pq.top();
pq.pop();
if (now.first!=dis[now.second]) continue;
if (now.second==t) break;
out(i,v,now.second)
{
LL len=v.c[i]+h[now.second]-h[to];
if (v.w[i] && dis[now.second]+len<dis[to])
{
dis[to]=dis[now.second]+len;
pq.push(mp(dis[to],to));
pre_node[to]=now.second;//记录从哪个点来
pre_edge[to]=i;//记录从哪条边来
}
}
}
if (dis[t]>=(LLONG_MAX>>1)) break;
LL miflow=LLONG_MAX>>1;
for(i=t;i!=s;i=pre_node[i])
cmin(miflow,v.w[pre_edge[i]]);//找到路径上流量最小值
for(i=t;~i;i=pre_node[i])
{
v.w[pre_edge[i]]-=miflow;
v.w[pre_edge[i]^1]+=miflow;//更新反向边
}
flow+=miflow;
cost+=miflow*(h[t]+dis[t]);
fo(i,1,n) h[i]=min(h[i]+dis[i],LLONG_MAX>>1);//更新h数组
}
}
void sol()
{
v.init();
LL i,x,y,z,w;
scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
fo(i,1,m)
{
scanf("%lld%lld%lld%lld",&x,&y,&z,&w);
v.add(x,y,z,w);
v.add(y,x,0,-w);
}
LL flow,cost;
MCMF(s,t,flow,cost);
printf("%lld %lld",flow,cost);
}
}solver;
int main()
{
solver.sol();
return 0;
}
例子:
黄色的边就是一个割
介绍一下网络流中最重要的定理之一------最小割最大流定理:
如果
1.
2.残留网络
3.存在一个割,
那么如何借此求出最小割呢?我们看可以先求出最大流,以S为起点在残留网络上DFS,搜索到的点就是包含S的一部分,剩余的则是另一部分,而最小割即是连接两部分的所有边.
1.飞行员配对方案问题(P2756)
一个二分图最大匹配问题,源点向所有外籍飞行员连容量为1的边,外籍飞行员向可以配对的英国飞行员连容量为1的边,英国飞行员向汇点连容量为1的边,网络最大流即是答案,若外籍飞行员连向英国飞行员的边满流,说明这一对被选中.
Code
2.太空飞行计划问题(P2762)
一个最大权闭合图问题,详细说明可以见胡伯涛的《最小割模型在信息学竞赛中的应用》.
简要说一下方法:源点向实验连容量为利润的边,实验向所需器材连容量为无穷大的边,器材向汇点连容量为花费的边,所有实验利润之和减去网络最小割的容量即是答案,方案可以按照上文方法输出.
Code
3.最小路径覆盖问题(P2764)
寻找最小路径条数等价于为尽可能多的点找到后继,于是就可以用一种类似于二分图匹配的方法求解.将点
Code