【学习笔记】Primal-Dual 原始对偶算法

Page Views Count

Johnson 全源最短路算法#

Floyd 可以 O(n3) 处理全源最短路,Bellman-Ford 单源最短路的复杂度是 O(nm) 的,Dijkstra 可以做到 O(mlogm) 但不能处理负边权,所以 Johnson 全源最短路算法通过处理使得可以用 n 次 Dijkstra 解决有负权图的全源最短路。

先建超级源点,向各点连边权为 0 的有向边,跑一次 Bellman-Ford,得到最短路 h,将 hu 作为 u 节点的势能,将 d(u,v) 改为 d(u,v)=d(u,v)+huhv

这样在最短路过程中,p1p2pn1pn,最短路值应该是:

d(p1,pn)=(d(p1,p2)+hp1hp2)++(d(pn1,pn)+hpn1+hpn)=d(p1,pn)+hp1hpn

注意到修改后的最短路较原来只和两端点势能有关,所以按照这个方法去做是可以找到最短路的。

同时根据 hu+d(u,v)hv,则 d(u,v)+huhv0,也就是一个正权图。

这样复杂度是 O(nmlogm)

Primal-Dual 原始对偶算法#

其实和上面类似,这个算法解决了不含负环的费用流,复杂度不再是 EK 的上界 O(nmf),其实就是改变了求最短路的算法。

依旧是从源点 S 开始跑 Bellman-Ford 记录势能 hu,边权改为 d(u,v)+huhv

问题在每次增广之后增加并减少了一些边。

这里的解决方案是,每次跑完 Dijkstra,把势能 hu 改为 hu+disu,这显然能代表最短路,证明和上面一样,关键是对边权是否全为正的讨论。

证明也是类似的,在上一次跑 Dijkstra 时,原有的边 (u,v)disu+(d(u,v)+huhv)disv,于是 d(u,v)+(hu+disu)(hv+disv)0,而新增加的边 (v,u) 的反向边一定在最短路上,于是 disu+(d(u,v)+huhv)=disv,进而 d(v,u)+(hv+disv)(hu+disu)=0,边权非负。

这样就可以在 O(nm+mlogmf) 的复杂度内解决问题,如果图有特殊性质,第一次的 Bellman-Ford 可以换成 BFS 之类的。

点击查看代码
int n,m;
int S,T;
struct edge{
    int to,nxt,w,c;
}e[maxm<<1];
int head[maxn],cnt;
inline void add_edge(int u,int v,int w,int c){
    e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt,e[cnt].w=w,e[cnt].c=c;
    e[++cnt].to=u,e[cnt].nxt=head[v],head[v]=cnt,e[cnt].w=0,e[cnt].c=-c;
}

bool vis[maxn];
ll h[maxn];
queue<int> Q;
inline void SPFA(){
    memset(vis,0,sizeof(vis));
    memset(h,0x3f,sizeof(h));
    vis[S]=true,h[S]=0;
    Q.push(S);
    while(!Q.empty()){
        int u=Q.front();
        Q.pop();
        vis[u]=false;
        for(int i=head[u],v,c;i;i=e[i].nxt){
            v=e[i].to,c=e[i].c;
            if(!e[i].w) continue;
            if(h[u]+c<h[v]){
                h[v]=h[u]+c;
                if(!vis[v]){
                    vis[v]=true;
                    Q.push(v);
                }
            }
        }
    }
}

struct Data{
    int u;
    ll d;
    Data()=default;
    Data(int u_,ll d_):u(u_),d(d_){}
    bool operator<(const Data &rhs)const{
        return d>rhs.d;
    }
};
priority_queue<Data> PQ;
ll dis[maxn];
int pre[maxn];
inline void Dijkstra(){
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    dis[S]=0;
    PQ.push(Data(S,0));
    while(!PQ.empty()){
        int u=PQ.top().u;
        PQ.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=head[u],v;i;i=e[i].nxt){
            v=e[i].to;
            ll c=e[i].c+h[u]-h[v];
            if(!e[i].w) continue;
            if(dis[u]+c<dis[v]){
                pre[v]=i;
                dis[v]=dis[u]+c;
                PQ.push(Data(v,dis[v]));
            }
        }
    }
}
inline pll MCMF(){
    pll res=make_pair(0,0);
    SPFA();
    while(1){
        Dijkstra();
        if(dis[T]==llinf) return res;
        int mn=inf;
        for(int u=T;u!=S;u=e[pre[u]^1].to) mn=min(mn,e[pre[u]].w);
        res.fir+=mn;
        for(int u=T;u!=S;u=e[pre[u]^1].to){
            res.sec+=1ll*e[pre[u]].c*mn;
            e[pre[u]].w-=mn,e[pre[u]^1].w+=mn;
        }
        for(int u=1;u<=n;++u) h[u]+=dis[u];
    }
}

作者:SoyTony

出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Primal-Dual_Algorithm.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   SoyTony  阅读(1284)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示