zkw学习笔记

本文详细介绍了ZKW算法在解决最小费用最大流问题中的应用,包括最小费用最大流的建立、负权边的消除方法以及算法的优化过程。通过修改顶标寻找增广路,确保在满足条件的最短路径上进行操作,提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文:http://www.artofproblemsolving.com/community/c1368h1020435


关于转化

     首先是1.最小费用(可行)流 $\to$ 最小费用最大流

建立超级源 $s'$ 和超级汇 $t'$ , 对顶点 $i$, 若 $e_i > 0$ 添加边 $s' \to i,c = 0,u = e_i$, 若 $e_i < 0$ 添加边 $ i\to t',c = 0,u = -e_i$, 之后求从 $s'$ 到 $t'$ 的最小费用最大流, 如果流量等于 $\sum{e_i}$, 就存在可行流, 残量网络已在原图上求出.

代码:

 

inline void New_Graph()
{
	for(int i=3;i<=con;i++)
	 if(e[i])
	    if(e[i]>0) addside(start,i,e[i],0);
	    else addside(i,end,-e[i],0);
	 else;
}

一开始脑子抽了没有理解为什么要用-ei后来黄学长跟我说ei<0  感觉自己蠢到家了。。。。。

2.最小费用最大流 $\to$ 最小费用(可行)流
  连边 $t \to s,c = -\infty,u = +\infty$, 所有点 $i$ 有 $e_i = 0$, 然后直接求.

3.最小费用(可行)流中负权边的消除

直接给负权边满流,然后转换成1

inline void Begin()
{
	int i,j,k,t;
	for(i=3;i<=con;i++)
	 for(Chain *tp=Head[i];tp;tp=tp->next)
	  if(tp->flow&&tp->cost<0)
	   {
	   	tp->pair->flow+=tp->flow;
	   	e[i]-=tp->flow;
	   	e[tp->u]+=tp->flow;
	   	con_cost=tp->flow*tp->cost;
	   	tp->flow=0;
	   }    
}


4.最小费用最大流中负权边的消除

先连边 $t \to s,c = 0,u = +\infty$, 使用 (3.) 中的方法消除负权边, 使用 (1.) 中的方法求出最小费用 (可行) 流, 之后距离标号不变, 再求最小费用最大流; 注意此时增广费用不能机械使用源点的标号——应该是源点汇点标号之差.


修改顶标

      其实zkw与km有相似之处  都是修改顶标来找增广路的

详细看原文:

这里使用的是连续最短路算法. 最短路算法? 为什么程序里没有 SPFA? Dijkstra? 且慢, 先让我们回顾一下图论中最短路算法中的距离标号. 定义 $D_i$ 为点 $i$ 的距离标号, 任何一个最短路算法保证, 算法结束时对任意指向顶点 $i$ 、从顶点 $j$ 出发的边满足 $D_i \le D_j + c_{ij}$(条件1), 且对于每个 $i$ 存在一个 $j$ 使得等号成立 (条件2). 换句话说, 任何一个满足以上两个条件的算法都可以叫做最短路, 而不仅仅是 SPFA、Dijkstra, 算法结束后, 恰在最短路上的边满足 $D_i = D_j + c_{ij}$.

  在最小费用流的计算中, 我们每次沿 $D_i = D_j + c_{ij}$ 的路径增广后都不会破坏条件 1, 但是可能破坏了条件 2. 不满足条件 2 的后果是什么呢? 使我们找不到每条边都满足 $ D_i = D_j + c_{ij}$ 新的增广路. 只好每次增广后使用 Dijkstra, SPFA 等等算法重新计算新的满足条件 2 的距离标号. 这无疑是一种浪费. KM 算法中我们可以修改不断修改可行顶标, 不断扩大可行子图, 这里也同样, 我们可以在始终满足条件 1 的距离标号上不断修改, 直到可以继续增广 (满足条件 2).

  回顾一下 KM 算法修改顶标的方法. 根据最后一次寻找交错路不成功的 DFS, 找到 $ d = \min_{i \in V, j \notin V}  \left\{ - w_{ij} + A_i + B_j \right\}$ , 左边的点增加 $d$, 右边的点减少 $d$ . 这里也一样, 根据最后一次寻找增广路不成功的 DFS, 找到 $ d = \min_{i \in V, j \notin V, u_{ij} > 0} \left\{ c_{ij} - D_i + D_j } \right\}$ , 所有访问过的点距离标号增加 $d$. 可以证明, 这样不会破坏性质 1, 而且至少有一条新的边进入了 $D_i = D_j + c_{ij}$ 的子图.

  算法的步骤就是初始标号设为 $0$ , 不断增广, 如果不能增广, 修改标号继续增广, 直到彻底不能增广: 源点的标号已经被加到了 $+\infty$ . 注意: 在程序中所有的 cost 均表示的是 reduced cost, 即 $c_{ij}^\pi = c_{ij} - D_i + D_j$. 另外, 这个算法不能直接用于有任何负权边的图. 更不能用于负权圈的情况. 有关这两种情况的处理, 参见 (2.) 和 (3.) 中的说明.

代码:

#include <cstdio>
#include <cstring>
using namespace std;
const int maxint=~0U>>1;

int n,m,pi1,cost=0;
bool v[550];
struct etype
{
    int t,c,u;
    etype *next,*pair;
    etype(){}
    etype(int t_,int c_,int u_,etype* next_):
        t(t_),c(c_),u(u_),next(next_){}
    void* operator new(unsigned,void* p){return p;}
} *e[550];

int aug(int no,int m)
{
    if(no==n)return cost+=pi1*m,m;
    v[no]=true;
    int l=m;
    for(etype *i=e[no];i;i=i->next)
        if(i->u && !i->c && !v[i->t])
        {
            int d=aug(i->t,l<i->u?l:i->u);
            i->u-=d,i->pair->u+=d,l-=d;
            if(!l)return m;
        }
    return m-l;
}

bool modlabel()
{
    int d=maxint;
    for(int i=1;i<=n;++i)if(v[i])
        for(etype *j=e[i];j;j=j->next)
            if(j->u && !v[j->t] && j->c<d)d=j->c;
    if(d==maxint)return false;
    for(int i=1;i<=n;++i)if(v[i])
        for(etype *j=e[i];j;j=j->next)
            j->c-=d,j->pair->c+=d;
    pi1 += d;
    return true;
}

int main()
{
    freopen("costflow.in","r",stdin);
    freopen("costflow.out","w",stdout);
    scanf("%d %d",&n,&m);
    etype *Pe=new etype[m+m];
    while(m--)
    {
        int s,t,c,u;
        scanf("%d%d%d%d",&s,&t,&u,&c);
        e[s]=new(Pe++)etype(t, c,u,e[s]);
        e[t]=new(Pe++)etype(s,-c,0,e[t]);
        e[s]->pair=e[t];
        e[t]->pair=e[s];
    }
    do do memset(v,0,sizeof(v));
    while(aug(1,maxint));
    while(modlabel());
    printf("%d\n",cost);
    return 0;
}

自己的可以应对负权的

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
char c;
inline void read(int &a)
{
	a=0;do c=getchar();while(c<'0'||c>'9');
	while(c<='9'&&c>='0')a=(a<<3)+(a<<1)+c-'0',c=getchar();
}
int con=2;
int Lastart=1,Laend=2;
int start,end;
struct Chain
{
	int u,cost,flow;
	Chain *next,*pair;
}*Head[100001];
int e[100001];
int con_cost;
int label[1000001];
int n,m;
inline void addside(int a,int b,int flow,int cost)
{
	Chain *tp=new Chain;
	tp->u=b;
	tp->flow=flow;
	tp->cost=cost;
	tp->next=Head[a];
	Head[a]=tp;
	tp=new Chain;
	tp->u=a;
	tp->flow=0;
	tp->cost=-cost;
	tp->next=Head[b];
	Head[b]=tp;
}
inline void Begin()
{
	int i,j,k,t;
	for(i=3;i<=con;i++)
	 for(Chain *tp=Head[i];tp;tp=tp->next)
	  if(tp->flow&&tp->cost<0)
	   {
	   	tp->pair->flow+=tp->flow;
	   	e[i]-=tp->flow;
	   	e[tp->u]+=tp->flow;
	   	con_cost=tp->flow*tp->cost;
	   	tp->flow=0;
	   }    
}
inline void New_Graph()
{
	for(int i=3;i<=con;i++)
	 if(e[i])
	    if(e[i]>0) addside(Lastart,i,e[i],0);
	    else addside(i,Laend,-e[i],0);
	 else;
}
bool visited[1000001];
const
   int INF=1<<29;
int DFS(int u,int F)
{
	if(u==Laend)return con_cost+=F*(label[end]-label[start]),F;
	int res=F;
	visited[u]=true;
	for(Chain *tp=Head[u];tp;tp=tp->next)
	     if(tp->flow&&!tp->cost&&!visited[tp->u])
	        {
	        	int d=DFS(tp->u,min(tp->flow,res));
	        	res-=d;
	        	tp->flow-=d;
	        	tp->pair->flow+=d;
	        	if(!res)return F;
			}
    return F-res;
}
inline bool updata()
{
	int a,d=INF,i;
	for(i=1;i<=con;i++)
	if(visited[i])
	  for(Chain *tp=Head[i];tp;tp=tp->next)
	     if(tp->flow&&!visited[tp->u]&&d>tp->cost)
	         d=tp->cost;
	if(d==INF)return false;
	
	for(i=1;i<=con;i++)
	   if(visited[i])
	      {
	      	label[i]+=d;
	      	for(Chain *tp=Head[i];tp;tp=tp->next)
	            tp->cost-=d,tp->pair->cost+=d;
		  }
    return true;
}
int main()
{
	read(n),read(m);
	start=3,con=end=n+2;
	addside(Lastart,start,INF,0);
	addside(end,Laend,INF,0);
	int a,b,v,d;
	for(int i=1;i<=m;i++)
	    read(a),read(b),read(v),read(d),addside(a,b,v,d);
	Begin();
	New_Graph();
	int maxflow=0;
	do
	   do
	    memset(visited,0,sizeof(visited));
	    while(maxflow+=DFS(Lastart,INF));
	while(updata());
     printf("%d %d\n",maxflow,con_cost);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值