题解:P3381 【模板】最小费用最大流
fengzhaoyu · · 题解
原始对偶算法是个好东西
思路
前置芝士:dinic-算法。
看看题解没有 dijkstra 加 dinic 的,这里来写一下。
首先要知道,网络流就是对于一条
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int N=5e4+15,M=5e5+15,inf=INT_MAX;
int read()
{
int t=1,x=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') t=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return t*x;
}
int cur[N],to[M],ne[M],f[M],w[M],d[N],vis[N];
int dis[N],hh[N];
int n,m,s,t,idx;
void add(int u,int v,int ww,int c)
{
to[idx]=v;
ne[idx]=hh[u];
f[idx]=ww;// 流量限制
w[idx]=c;// 价格
hh[u]=idx++;
}
bool spfa()//求最短路并判断此时是否存在增广路
{
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++) dis[i]=inf;
queue<int>q;
q.push(s);
vis[s]=1;
dis[s]=0;
d[s]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=hh[u];i!=-1;i=ne[i])
{
int v=to[i];
if(f[i]&&dis[v]>dis[u]+w[i])
{
dis[v]=dis[u]+w[i];
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
if(dis[t]<inf) return 1;
return 0;
}
int ans1=0,ans2=0;
int dfs(int u,int mf)
{
if(u==t) return mf;
int sum=0;
vis[u]=1;//这样是为了下文判断是否存在负环,如果有,可直接跳过
for(int i=cur[u];i!=-1;i=ne[i])
{
cur[u]=i;//当前弧优化
int v=to[i];
if(f[i]&&dis[v]==dis[u]+w[i]&&vis[v]==0)//保证了一定会按最短路增广
{
int ff=dfs(v,min(mf,f[i]));
f[i]-=ff;
f[i^1]+=ff;
sum+=ff;
mf-=ff;
ans2+=ff*w[i];//由于dinic是多路增广,所以只能边遍历便更新值
if(mf==0) break;
}
}
vis[u]=0;
// if(sum==0) d[u]=0;
return sum;
}
void Dinic()
{
while(spfa())
{
memcpy(cur,hh,sizeof hh);
ans1+=dfs(s,inf);
}
printf("%d %d\n",ans1,ans2);
}
int main()
{
memset(hh,-1,sizeof hh);
n=read(),m=read(),s=read(),t=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read(),c=read();
add(u,v,w,c);
add(v,u,0,-c);
}
Dinic();
return 0;
}
但是,我们知道,关于 spfa,它已经死了远不如 dijkstra,那有没有一种方法使此题能用 dijkstra 呢?有,原始对偶算法。我们先想,此题本来不用 dijkstra 的原因是因为有负环,那我们是否能变负为正呢?
-
引入势函数(对偶变量)
h_{u} ,为每个节点v 维护一个势能值。 -
重新定义边权:对每条边
u\rightarrow v ,定义修正边权:c'_{u,v}=c_{u,v}+h_u−h_v 。 -
其中
c_{u,v} 是原始费用。可以通过设置合适的h_u ,使得所有修正权c'_{u,v}\geq 0 ,从而允许使用 dijkstra 算法。 -
那么由此可得:
c_{u,v}+h_u-h_v\geq 0 ,将h_v 移项——不就是最短路中的松弛操作吗?那好,初始势函数的值可以用从s 出发的最短路来表示。(正确性的证明:对于一条增广路,一条边c'_{i,j}=c_{i,j}+h_i-h_j ,它的下一条边c'_{j,k}=c_{j,k}+h_j-h_k ,当它们相加时,h_j 抵消了!由此推出:\sum c'_{u,v}=\sum c_{u,v}+h_s-h_t ,因此,路径的实际费用与修正费用仅相差一个常数,与路径无关。也就是说,每次增广多带来的值是一样的。) -
设 d{v} 是当前残量网络中从
s 到v 的最短修正距离(dijkstra 得),势函数更新为 $h{newv}=h{oldv}+d{v}。此时是否仍然保持非负性?对于一条边, dv\leq d{u}+c'_{u,v}=du+c{u,v}+h{oldu}-h{oldv},整理得: c{u,v}+h{oldu}+du-h{oldv}+dv\geq 0,其中 h{oldu}+du-h{oldv}$ 就是势函数更新了。代码
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int N=1e5+15,inf=INT_MAX;
int read()
{
int t=1,x=0;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') t=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return t*x;
}
int cur[N],to[N],ne[N],f[N],w[N],vis[N];
int dis[N],h[N],hh[N];
int n,m,s,t,idx;
void add(int u,int v,int ww,int c)
{
to[idx]=v;
ne[idx]=hh[u];
f[idx]=ww;
w[idx]=c;
hh[u]=idx++;
}
void spfa()//求h初始值,因为只跑一次,所以不会慢
{
memset(h,0x3f,sizeof h);
queue<int>q;
q.push(s);
vis[s]=1;
h[s]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=hh[u];i!=-1;i=ne[i])
{
int v=to[i];
if(f[i]&&h[v]>h[u]+w[i])
{
h[v]=h[u]+w[i];
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
}
bool dijkstra()
{
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++) dis[i]=inf;
priority_queue<pii,vector<pii>,greater<pii> >q;
q.push({0,s});
dis[s]=0;
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=hh[u];i!=-1;i=ne[i])
{
int v=to[i];
if(f[i]&&dis[v]>dis[u]+w[i]+h[u]-h[v])
{
dis[v]=dis[u]+w[i]+h[u]-h[v];
q.push({dis[v],v});
}
}
}
return dis[t]!=inf;
}
int ans1=0,ans2=0;
int dfs(int u,int mf)
{
if(u==t) return mf;
int sum=0;
vis[u]=1;
for(int i=cur[u];i!=-1;i=ne[i])
{
cur[u]=i;
int v=to[i];
if(f[i]&&vis[v]==0&&h[v]==h[u]+w[i])
{
// cout<<"ll";
int ff=dfs(v,min(mf,f[i]));
f[i]-=ff;
f[i^1]+=ff;
sum+=ff;
mf-=ff;
ans2+=ff*w[i];
if(mf==0) break;
}
}
vis[u]=0;
return sum;
}
void Dinic()
{
spfa();
while(dijkstra())
{
memset(vis,0,sizeof vis);
memcpy(cur,hh,sizeof hh);
for(int i=1;i<=n;i++) h[i]+=dis[i];
int k=dfs(s,inf);
ans1+=k;
}
printf("%d %d\n",ans1,ans2);
}
int main()
{
memset(hh,-1,sizeof hh);
n=read(),m=read(),s=read(),t=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read(),c=read();
add(u,v,w,c);
add(v,u,0,-c);
}
Dinic();
return 0;
}
完美撒花!