zkw费用流 java_zkw费用流的算法看不懂,请问里面etype代表什么啊,路径又是怎么存储的啊,可以讲解一下吗?...

#include

#include

using namespace std;

const int maxint = ~0U>>1;

int n, m, pil, cost = 0;

// n为顶点数,m为边数,pil记录当前增广路径上的消耗,cost记录总消耗

bool v[550];// 记录访问过的顶点

// 边的结构

struct etype{

int t, c, u; // t为该边指向的顶点,c为标签,初始为给定的单位代价,

// c用作能否增广的标记,在增广过程中只有c被标记成0才能增广,这与算法原始描述里修改顶点标记D[i]不太一样

// u为该边的容量(upper limit)

etype * next, * pair; // *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;

} // 不清楚为什么要重载new运算符?

}* e[550]; // e的每个元素都是指向etype的指针,即边etype的数组

// 增广函数,从no顶点开始增广,m为到该顶点时最多能增广的流量,

// 返回用掉的流量,在DFS回溯过程中依次减去用掉的流量。

int aug(int no, int m) {

if(no == n)//当前已到终点,pil为当前增广路径的单位消耗,m为之前增广路径能增广的流量

return cost += pil * m, m;

v[no] = true;// 把当前顶点设为已访问

int l = m; // l用于后面记录剩余流量

// DFS进行增广,for循环里遍历从顶点no发出的每一条边,即遍历顶点no的邻接表

for(etype* i=e[no]; i; i=i->next)

// 如果边i仍有剩余流量,且c被标记为0,满足增广条件,且另一头未被访问

if(i->u && !i->c && !v[i->t]){

// DFS增广下一条边,流量为之前能增广的流量和当前边剩余上界的最小值

// 返回d为用掉的流量

int d = aug(i->t, l < i->u ? l : i->u);

// 当前边剩余容量上界减用掉的d,相反方向的对边加上d,代表可以退回的流量

// 回溯,l减掉用掉的d即为剩余的流量

i->u -= d, i->pair->u += d, l -= d;

// 如果l已经用完,返回用掉的流量m

if(!l) return m;

}

// l为剩下的流量,m-l即为用掉的流量

// 如果DFS达不到终点,则 m-l 为0

return m - l;

}

bool modlabel(){

int d = maxint; // d记录能重标签的最小差距

// 即从已访问过的点到未访问过的点的所有边中,至少改变多少就能使一条边的c标记成0

for(int i=1; i<=n; ++i) // 检查所有顶点

if(v[i]) //从已DFS访问过的顶点出发

for(etype* j=e[i]; j; j=j->next) // 遍历从i出发的所有边,即邻接表

// 如果该边有剩余容量,且未被访问,且该边标记c小于最小差距

if(j->u && !v[j->t] && j->c < d)

d = j->c; // d始终保持重标记c需要的最小差距

if(d == maxint) // 如果已经没法再重标签了,就没法再增广,返回false

return false;

// 将所有从已访问过的点到未访问过的点的边的标记c都减去d,

// 反方向加上d,即重标签过程。

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;

// 重标签结束后至少有一条从已访问顶点到未访问顶点的边满足c==0,即可以增广

pil += d; // pil记录增广路径上的单位代价,因为pil每次单调递增最小的差额

// 所以能始终保存增广路径上的单位代价。

return true;

}

int main()

{

freopen("costflow.in","r",stdin);

freopen("costflow.out","w",stdout);

scanf("%d %d",&n,&m);//n为顶点数,m为边数

etype *Pe=new etype[m+m];//正向加反向的边,因此为2m

while(m--) //存储每一条边

{

int s,t,c,u;

scanf("%d%d%d%d",&s,&t,&u,&c);

//与算法原文描述不同,这里用c来作为标记,而不是顶标D[i]

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];//互为pair

e[t]->pair=e[s];

}

do

do memset(v, 0, sizeof(v));// 每次回溯到源点就把所有顶点访问记录都设为0,即未访问,从头开始

while(aug(1, maxint));// 1代表从源点开始增广,源点流量无穷即有maxint的流量可以增广

while(modlabel()); // 内循环无法增广时就修改标签,这是zkw参考km重标记法重要的思路

// 当重标签无法改变时,循环结束,算法结束。

printf("%d\n",cost);

return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值