一、题目
二、解法
讲一种势能 ( t dijkstra) 的做法(简称势能算法),因为 ( t spfa) 在单次扩展的时候可能会被卡到 (O(nm)),而势能 ( t dijkstra) 的时间复杂度是严格的 (O(mlog n)),在一些扩展次数较小的毒瘤题中可能会用到。
势能算法的中心思路是化负权边为非负权边,我们设计势能函数 (h(x)) 来调整边权,这里的势能和物理中的定义是很相似的,也就是势能变化之和初末位置有关,而和路径无关。
令 (w'(u,v)=w(u,v)+h(u)-h(v)),不难发现最后跑出来的最短路要加上 (h(t)-h(s))
关键是如何使得 (w(u,v)+h(u)-h(v)geq0) 恒成立,设 (d(u)) 为 (s) 到 (u) 真正的最短路,设 (dis(u)) 为 (s) 到 (u) 通过转化后的边求出来的最短路,令 (h(s)=0),有 (dis(u)+h(u)=d(u))
如果我们再每次做完最短路之后让 (h(u)leftarrow d(u)) 是满足条件的,因为如果是原来的边那么显然 (d(u)-d(v)+w(u,v)geq 0),如果是新产生的取负的边那么 (d(u)=d(v)+w,(w>0) ightarrow d(u)-d(v)-w=0),新的边权就是 (0),也满足条件。那么每次我们把 (h(u)) 加上 (dis(u)) 就可以维护势能函数了。
时间复杂度 (O(flowcdot mlog n))
#include <cstdio>
#include <queue>
using namespace std;
const int M = 50005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,s,t,tot,f[M],dis[M],fw[M],pre[M],lst[M],h[M];
struct edge
{
int v,f,c,next;
}e[2*M];
struct node
{
int u,c;
bool operator < (const node &b) const
{
return c>b.c;
}
};
void add(int u,int v,int F,int c)
{
e[++tot]=edge{v,F,c,f[u]},f[u]=tot;
e[++tot]=edge{u,0,-c,f[v]},f[v]=tot;
}
int bfs()
{
for(int i=1;i<=n;i++) dis[i]=inf;
fw[s]=inf;pre[s]=lst[s]=dis[s]=0;
priority_queue<node> q;
q.push(node{s,0});
while(!q.empty())
{
node t=q.top();q.pop();int u=t.u;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,c=h[u]-h[v]+e[i].c;
if(dis[v]>dis[u]+c && e[i].f)
{
dis[v]=dis[u]+c;
pre[v]=u;lst[v]=i;
fw[v]=min(fw[u],e[i].f);
q.push(node{v,dis[v]});
}
}
}
return dis[t]<inf;
}
signed main()
{
n=read();m=read();s=read();t=read();tot=1;
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),f=read(),c=read();
add(u,v,f,c);
}
int ans=0,flow=0;
while(bfs())
{
int zy=t;
flow+=fw[t];
ans+=(dis[t]+h[t])*fw[t];
while(zy!=s)
{
e[lst[zy]].f-=fw[t];
e[lst[zy]^1].f+=fw[t];
zy=pre[zy];
}
for(int i=1;i<=n;i++)
h[i]+=dis[i];
}
printf("%d %d
",flow,ans);
}