费用流

费用流,就是在网络流的基础上加入了每条边单位流量的费用,求最大流量和在最大流量的情况下费用的最小值的问题。

对于这种问题,基本思路当然还是在最大流问题的基础上修改啊

所以在这里我给出2种解法:(例题: P3381 【模板】最小费用最大流

1、EK-改

当然这也是目前的主流写法,在EK中,将BFS过程换成SPFA的过程,松弛与增广一起进行,当然是很美妙的写法。当然整体的代码和EK没有什么特别大的区别,就不多讲了。

下面是代码(1604ms)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <bits/stdc++.h>
#define N 200000+110
#define inf 0x7fffffff
using namespace std;
int n,m,S,T,cnt,ans,Ans;
int head[N],q[N],pre[N],dist[N],mn[N];
bool vis[N];
struct edge
{
int to,nxt,weigh,cost;
}e[N];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline int add(int u,int v,int w,int f)
{
e[cnt]=(edge){v,head[u],w,f};
head[u]=cnt++;
}
inline bool SPFA(int s,int t)
{
for(register int i=1;i<=n;i++)
dist[i]=inf,vis[i]=0,pre[i]=-1;
int fr=1,tl=0;
q[1]=s,vis[s]=1,mn[s]=inf,dist[s]=0;
while(tl<fr)
{
int u=q[++tl];
vis[u]=0;
for(register int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(dist[v]>dist[u]+e[i].cost&&e[i].weigh>0)
{
dist[v]=dist[u]+e[i].cost;
mn[v]=min(e[i].weigh,mn[u]);
pre[v]=i;
if(!vis[v])
{
q[++fr]=v;
vis[v]=1;
}
}
}
}
return dist[t]!=inf;
}
inline void EK(int s,int t)
{
while(SPFA(s,t))
{
int x=t;
while(x!=s)
{
int i=pre[x];
e[i].weigh-=mn[t];
e[i^1].weigh+=mn[t];
x=e[i^1].to;
}
Ans+=mn[t];
ans+=dist[t]*mn[t];
}
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read(),S=read(),T=read();
for(register int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read(),f=read();
add(u,v,w,f);
add(v,u,0,-f);
}
EK(S,T);
printf("%d %d",Ans,ans);
return 0;
}

2、ZKW

首先%ZKW大神:从入门到精通: 最小费用流的“zkw算法”

是不是觉得EK实在是慢得惊人呢?那么我们来看一下ZKW神犇发明的“ZKW算法”。当然,按照ZKW本人的意思,我下面贴的这段代码并不是“ZKW费用流”,因为它用到了SPFA。ZKW本人的代码可以在上面的链接里找到,以下是我个人的见解(因为我不会KM啊)

这里呢,我们用了一个DFS,它的用处就在于多路增广,可以降低某些图上的复杂度。有这个基础的话就可以接受SPFA的复杂度了,而且也可以在有负权的图上跑了。

以下是代码(932ms)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <bits/stdc++.h>
#define N 200000+110
#define inf 0x7fffffff
using namespace std;
int n,m,S,T,cnt,ans;
int deep[N],head[N],q[N],dist[N];
bool vis[N];
struct edge
{
int to,nxt,weigh,cost;
}e[N];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline void add(int u,int v,int w,int f)
{
e[cnt]=(edge){v,head[u],w,f};
head[u]=cnt++;
}
inline bool SPFA(int s,int t)
{
for(register int i=1;i<=n;i++)
dist[i]=inf,vis[i]=0;
dist[t]=0;
vis[t]=1;//首先SPFA我们维护距离标号的时候要倒着跑,这样可以维护出到终点的最短路径
int fr=1,tl=0;
q[1]=t;
while(tl<fr)
{
int u=q[++tl];
for(register int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(e[i^1].weigh&&dist[v]>dist[u]-e[i].cost)//首先c[k^1]是为什么呢?因为我们要保证正流,但是SPFA是倒着跑的,所以说我们要求c[k]的对应反向边是正的,这样保证走的方向是正确的
{
dist[v]=dist[u]-e[i].cost;//因为已经是倒着的了,我们也可以很清楚明白地知道建边的时候反向边的边权是负的,所以减一下就对了(负负得正)
if(!vis[v])
{
vis[v]=1;
q[++fr]=v;
}
}
}
vis[u]=0;
}
return dist[s]<inf
}
inline int dfs(int u,int t,int dis)
{
if(u==t)
{
vis[t]=1;
return dis;
}
vis[u]=1;
int used=0;
for(register int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(!vis[v]&&e[i].weigh&&dist[u]-e[i].cost==dist[v])//增广的条件(虽然我不太懂)
{
int a=dfs(v,t,min(e[i].weigh,dis-used));
if(a)
{
ans+=a*e[i].cost;
e[i].weigh-=a;
e[i^1].weigh+=a;
used+=a;
}
if(used==dis)
break;
}
}
return used;
}
inline int CostFlow(int s,int t)
{
int flow=0;
while(SPFA(s,t))
{
vis[t]=1;
while(vis[t])
{
for(register int i=1;i<=n;i++)
vis[i]=0;
flow+=dfs(s,t,inf);
}
}
return flow;
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read(),S=read(),T=read();
for(register int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read(),f=read();
add(u,v,w,f);
add(v,u,0,-f);
}
printf("%d ",CostFlow(S,T));
printf("%d",ans);;
return 0;
}

以上就是一般的ZKW(伪)

SLF优化

那么如果对这个效率还不满意呢?

对了,向标题所说的那样,进行SLF优化吧(虽然我不懂)

代码如下(860ms)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <bits/stdc++.h>
#define N 200000+110
#define inf 0x7fffffff
using namespace std;
int n,m,S,T,cnt,ans;
int deep[N],head[N],dist[N];
bool vis[N];
struct edge
{
int to,nxt,weigh,cost;
}e[N];
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
inline void add(int u,int v,int w,int f)
{
e[cnt]=(edge){v,head[u],w,f};
head[u]=cnt++;
}
inline bool SPFA(int s,int t)
{
for(register int i=1;i<=n;i++)
dist[i]=inf,vis[i]=0;
dist[t]=0;
vis[t]=1;
deque<int>q;
q.push_back(t);
while(!q.empty())
{
int u=q.front();
q.pop_front();
for(register int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(e[i^1].weigh&&dist[v]>dist[u]-e[i].cost)
{
dist[v]=dist[u]-e[i].cost;
if(!vis[v])
{
vis[v]=1;
if(!q.empty()&&dist[v]<dist[q.front()])
q.push_front(v);
else
q.push_back(v);
}
}
}
vis[u]=0;
}
if(dist[s]<inf)
return 1;
else
return 0;
}
inline int dfs(int u,int t,int dis)
{
if(u==t)
{
vis[t]=1;
return dis;
}
vis[u]=1;
int used=0;
for(register int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(!vis[v]&&e[i].weigh&&dist[u]-e[i].cost==dist[v])
{
int a=dfs(v,t,min(e[i].weigh,dis-used));
if(a)
{
ans+=a*e[i].cost;
e[i].weigh-=a;
e[i^1].weigh+=a;
used+=a;
}
if(used==dis)
break;
}
}
return used;
}
inline int CostFlow(int s,int t)
{
int flow=0;
while(SPFA(s,t))
{
vis[t]=1;
while(vis[t])
{
for(register int i=1;i<=n;i++)
vis[i]=0;
flow+=dfs(s,t,inf);
}
}
return flow;
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read(),S=read(),T=read();
for(register int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read(),f=read();
add(u,v,w,f);
add(v,u,0,-f);
}
printf("%d ",CostFlow(S,T));
printf("%d",ans);;
return 0;
}

看完代码和上面的一比较,你们大概会觉得没什么区别吧,说起来也差不多,就是SPFA的时候用了个双端队列的STL,然后入队时加入了一点玄学特判,总之就是这样了,反正这样就快一些就是了。

【注】本段部分引用一种更高效的费用流算法——zkw费用流


然后费用流就讲完了DA☆ZE!

0%