网络流

早过忘川 提交于 2020-01-22 21:05:39

一个网络\(G=(V,E)\)是一张有向图,图中每条有向边\((x,y)\in E\)都有一个给定的权值\(c(x,y)\),称为边的容量。特别地,若\((x,y) \notin E\),则\(c(x,y)=0\)。图中还有两个指定的特殊节点\(S,T \in V(S \neq T)\)分别被称为源点和汇点。

\(f(x,y)\)是定义在节点二元组\((x \in V,y \in V)\)上的实数函数,且满足:

容量限制:\(f(x,y) \leq c(x,y)\)

斜对称:\(f(x,y)=-f(y,x)\)

流量守恒:\(\forall x \neq S,\ x \neq T,\ \sum_{(u,x)\ \in E} f(u,x) = \sum_{(x,v)\ \in E}f(x,v)\)

\(f\)称为网络的流函数,对于\((x,y) \in E\)\(f(x,y)\)称为边的流量,\(c(x,y)-f(x,y)\)称为边的剩余流量

\(\sum_{(S,v)\ \in E} f(S,v)\)称为整个网络的流量(\(S\)为源点)

最大流

Edmond—Karp算法

若一条从源点\(S\)到汇点\(T\)的路径上各条边的剩余容量都大于\(0\),则称这条路径为一条增广路

\(EK\)算法为用\(bfs\)不断寻找增广路,直到网络上不存在增广路为止

\(bfs\)找到任意一条从\(S\)\(T\)的路径,记录路径上各边的剩余容量的最小值,则网络的流量就可以增加这个最小值

利用邻接表成对存储来实现\((x,y)\)剩余容量的减小,\((y,x)\)剩余容量的增大

时间复杂度上界为\(O(nm^2)\),一般可以处理\(1e3 \sim 1e4\)规模的网格

\(code\)

bool bfs()
{
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(s);
    vis[s]=true;
    res[s]=inf;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(vis[y]||!v) continue;
            res[y]=min(res[x],v);
            pre[y]=i;
            q.push(y);
            vis[y]=true;
        }
    }
    return vis[t];
}
void update()
{
    int x=t;
    while(x!=s)
    {
        int i=pre[x];
        e[i].v-=res[t];
        e[i^1].v+=res[t];
        x=e[i^1].to;
    }
    ans+=res[t];
}

......

while(bfs()) update();

Dinic算法

在任意时刻,网络中所有节点以及剩余容量大于\(0\)的边构成的子图称为残量网络

\(Dinic\)算法引入分层图的概念,\(d[x]\)表示从\(S\)\(x\)最少需要经过的边数,为了方便处理设\(d[S]=1\),分层图为残量网络中满足\(d[y]=d[x]+1\)的边\((x,y)\)构成的子图

时间复杂度上界为\(O(n^2m)\),一般可以处理\(1e4 \sim 1e5\)规模的网格,求解二分图最大匹配的时间复杂度为\(O( \sqrt nm)\)

\(Dinic\)算法中还可以加入若干剪枝来优化

\(res\),表示当前节点的流量剩余,若\(res \leqslant 0\),停止寻找增广路

\(cur[x]\),表示当到达到\(x\)节点时,直接从\(cur[x]\)对应的边开始遍历,实际表示上一次从\(x\)遍历到了哪一条边,因为在这之间的边都已经被彻底增广过了,所以可以直接跳转,称为当前弧优化

\(code\)

bool bfs()
{
    memcpy(cur,head,sizeof(head));
    memset(d,0,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(d[y]||!v) continue;
            d[y]=d[x]+1;
            q.push(y);
        }
    }
    return d[t];
}
int dfs(int x,int lim)
{
    if(x==t) return lim;
    int res=lim,flow;
    for(int &i=cur[x];i;i=e[i].nxt)
    {
        int y=e[i].to,v=e[i].v;
        if(d[y]!=d[x]+1||!v) continue;
        if(flow=dfs(y,min(res,v)))
        {
            res-=flow;
            e[i].v-=flow;
            e[i^1].v+=flow;
            if(!res) break;
        }
    }
    return lim-res;
}
int dinic()
{
    int flow,ans=0;
    while(bfs())
        while(flow=dfs(s,inf))
            ans+=flow;
    return ans;
}

若要求每条边的所用的流量,可以将原图备份,跑完最大流后,用原图的容量减去当前的剩余容量即可求得所用流量

可以去求解二分图多重匹配

利用最小割,将求解最大收益转化为最小代价

平面图最小割\(=\)对偶图最短路,应用有狼抓兔子海拔

费用流

Edmond—Karp算法

\(code\)

bool spfa()
{
    for(int i=1;i<=n;++i) dis[i]=inf;
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(s);
    vis[s]=true;
    dis[s]=0;
    res[s]=inf;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=false;
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v,c=e[i].c;
            if(dis[y]>dis[x]+c&&v)
            {
                dis[y]=dis[x]+c;
                res[y]=min(res[x],v);
                pre[y]=i;
                if(!vis[y])
                {
                    vis[y]=true;
                    q.push(y);
                }
            }
        }
    }
    return dis[t]!=inf;
}
void update()
{
    int x=t;
    while(x!=s)
    {
        int i=pre[x];
        e[i].v-=res[t];
        e[i^1].v+=res[t];
        x=e[i^1].to;
    }
    ans+=res[t];
    sum+=res[t]*dis[t];
}

......

while(spfa()) update();

Dinic算法

\(code\)

bool spfa()
{
    for(int i=1;i<=n;++i) dis[i]=inf;
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(s);
    dis[s]=0;
    vis[s]=true;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        vis[x]=false;
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v,c=e[i].c;
            if(dis[y]>dis[x]+c&&v)
            {
                dis[y]=dis[x]+c;
                if(!vis[y])
                {
                    vis[y]=true;
                    q.push(y);
                }
            }
        }
    }
    return dis[t]!=inf;
}
int dfs(int x,int lim)
{
    if(x==t) return lim;
    vis[x]=true;
    int res=lim,flow;
    for(int i=head[x];i;i=e[i].nxt)
    {
        int y=e[i].to,v=e[i].v,c=e[i].c;
        if(dis[y]!=dis[x]+c||!v||vis[y]) continue;
        if(flow=dfs(y,min(res,v)))
        {
            res-=flow;
            e[i].v-=flow;
            e[i^1].v+=flow;
            if(!res) break;
        }
    }
    return lim-res;
}
void dinic()
{
    int flow; 
    while(spfa())
        while(flow=dfs(s,inf))
            ans+=flow,sum+=flow*dis[t];
} 

可以去求解二分图带权匹配

有上下界限制的网络流

无源汇有上下界可行流

\(n\)个点,\(m\)条边的网络,求一个可行解,使得边\((x,y)\)的流量介于\([\ low_{x,y},up_{x,y}\ ]\)之间,并且整个网络满足流量守恒

\(up_{x,y}-low_{x,y}\)作为容量上界,\(0\)作为容量下界

\(in[x]=\sum\limits_{i\to x} low(i,x)-\sum\limits_{x\to i} low(x,i)\)

\(in[x]>0\),则从源点\(S\)\(x\)连边,容量为\(in[x]\),反之,则从\(x\)向汇点\(T\)连边,容量为\(-in[x]\)

在该网络上求最大流,求完后每条边的流量再加上容量下界即为一种可行流

\(code:\)

bool bfs()
{
    memcpy(cur,head,sizeof(head));
    memset(d,0,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(d[y]||!v) continue;
            d[y]=d[x]+1;
            q.push(y);
        }
    }
    return d[t];
}
int dfs(int x,int lim)
{
    if(x==t) return lim;
    int res=lim,k;
    for(int &i=cur[x];i;i=e[i].nxt)
    {
        int y=e[i].to,v=e[i].v;
        if(d[y]!=d[x]+1||!v) continue;
        if(k=dfs(y,min(res,v)))
        {
            res-=k;
            e[i].v-=k;
            e[i^1].v+=k;
            if(!res) break;
        }
    }
    return lim-res;
}
int dinic()
{
    int flow,ans=0;
    while(bfs())
        while(flow=dfs(s,inf))
            ans+=flow;
    return ans;
}
bool check()
{
    for(int i=head[s];i;i=e[i].nxt)
        if(e[i].v)
            return false;
    return true;
}

......

for(int i=1;i<=m;++i)
{
    int a,b,up;
    read(a),read(b),read(low[i]),read(up);
    in[a]-=low[i],in[b]+=low[i];
    add(a,b,up-low[i]);
}
for(int i=1;i<=n;i++)
{   
    if(in[i]>0) add(s,i,in[i]);
    else add(i,t,-in[i]);
}
dinic();
if(check()) 
{
    puts("YES");
    for(int i=1;i<=m;i++) printf("%d\n",e[(i<<1)^1].v+low[i]);
}
else puts("NO");

有源汇有上下界最大流

\(T\)\(S\)连一条容量上界为\(inf\),容量下界为\(0\)的边,使有源汇转化为无源汇

在残量网络上再求原源点到原汇点的最大流

\(code:\)

bool bfs()
{
    memcpy(cur,head,sizeof(head));
    memset(d,0,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(d[y]||!v) continue;
            d[y]=d[x]+1;
            q.push(y);
        }
    }
    return d[t];
}
int dfs(int x,int lim)
{
    if(x==t) return lim;
    int res=lim,k;
    for(int &i=cur[x];i;i=e[i].nxt)
    {
        int y=e[i].to,v=e[i].v;
        if(d[y]!=d[x]+1||!v) continue;
        if(k=dfs(y,min(res,v)))
        {
            res-=k;
            e[i].v-=k;
            e[i^1].v+=k;
            if(!res) break;
        }
    }
    return lim-res;
}
int dinic()
{
    int flow,ans=0;
    while(bfs())
        while(flow=dfs(s,inf))
            ans+=flow;
    return ans;
}
bool check()
{
    for(int i=head[s];i;i=e[i].nxt)
        if(e[i].v)
            return false;
    return true;
}

......

for(int i=1;i<=m;++i)
{
    int a,b,up,low;
    read(a),read(b),read(low),read(up);
    in[a]-=low,in[b]+=low;
    add(a,b,up-low);
}
for(int i=1;i<=n;i++)
{   
    if(in[i]>0) add(s,i,in[i]);
    else add(i,t,-in[i]);
}
add(T,S,inf);
dinic();
ans=e[edge_cnt].v;
e[edge_cnt].v=e[edge_cnt^1].v=0;
if(check()) 
{
    s=S,t=T;
    printf("%d",ans+dinic());
}
else puts("NO");

有源汇有上下界最小流

先不添加 \(T\)\(S\) 的边,求一次超级源到超级汇的最大流。

然后再添加一条从 \(T\)\(S\) 下界为 \(0\) ,上界为 \(inf\) 的边,在残量网络上再求一次超级源到超级汇的最大流

流经 \(T\)\(S\) 的边的流量就是最小流的值

\(code:\)

bool bfs()
{
    memcpy(cur,head,sizeof(head));
    memset(d,0,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(d[y]||!v) continue;
            q.push(y);
            d[y]=d[x]+1;
        }
    }
    return d[t];
}
int dfs(int x,int lim)
{
    if(x==t) return lim;
    int res=lim,k;
    for(int &i=cur[x];i;i=e[i].nxt)
    {
        int y=e[i].to,v=e[i].v;
        if(d[y]!=d[x]+1||!v) continue;
        if(k=dfs(y,min(res,v)))
        {
            res-=k;
            e[i].v-=k;
            e[i^1].v+=k;
            if(!res) break;
        }
    }
    return lim-res;
}
int dinic()
{
    int k,flow=0;
    while(bfs())
    {
        while(k=dfs(s,inf))
        {
            flow+=k; 
        }
    }
    return flow;
}
bool check()
{
    for(int i=head[s];i;i=e[i].nxt)
        if(e[i].v)
            return false;
    return true;
}

......

for(int i=1;i<=m;++i)
{
    int a,b,up,low;
    read(a),read(b),read(low),read(up);
    in[a]-=low,in[b]+=low;
    add(a,b,up-low);
}
for(int i=1;i<=n;i++)
{   
    if(in[i]>0) add(s,i,in[i]);
    else add(i,t,-in[i]);
}
dinic();
add(T,S,inf);
dinic();
if(!check())
{
    puts("please go home to sleep");
    return 0;
}
printf("%d",e[edge_cnt].v);

最大权闭合子图

若有向图 \(G\) 的子图 \(V\) 满足: \(V\) 中顶点的所有出边均指向 \(V\) 内部的顶点,则称 \(V\)\(G\) 的一个闭合子图

\(G\) 中的点有点权,则点权和最大的闭合子图称为有向图 \(G\) 的最大权闭合子图

建立源点 \(S\) 和汇点 \(T\) ,源点 \(S\) 连所有点权为正的点,容量为该点点权;其余点连汇点 \(T\) ,容量为该点点权的相反数,对于原图中的边 \((x,y)\) ,连边 \((x,y,inf)\)

最大权闭合图的点权和 \(=\) 所有正权点权值和 \(-\) 最小割

也就是最大收益转化为了最小代价

在残量网络中由源点 \(S\) 能够访问到的点,就构成一个点数最少的最大权闭合图

最大密度子图

一个无向图\(G=(V,E)\)的边数\(|E|\)与点数\(|V|\)的比值\(D=\frac{|E|}{|V|}\)称为它的密度

\(G\)的一个子图\(G^\prime=(V^\prime,E^\prime)\),使得\(D^\prime=\frac{|E^\prime|}{|V^\prime|}\)最大

二分\(g\leqslant\frac{|E|}{|V|}\)

\(|E|-|V|×g \geqslant0\)

源点\(S\)向所有边连容量为\(1\)的边,边向其两端的点连容量为\(inf\)的边,点向汇点\(T\)连容量为\(g\)的边

二分下界:\(\frac{1}{n}\),上界:\(m\),精度:\(\frac{1}{n^2}\)

\(code:\)

bool bfs()
{
    for(int i=s;i<=t;++i) cur[i]=head[i];
    memset(d,0,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to;
            double v=e[i].v;
            if(d[y]||fabs(v)<eps) continue;
            d[y]=d[x]+1;
            q.push(y);
        }
    }
    return d[t];
}
double dfs(int x,double lim)
{
    if(x==t) return lim;
    double res=lim,flow;
    for(int &i=cur[x];i;i=e[i].nxt)
    {
        int y=e[i].to;
        double v=e[i].v;
        if(d[y]!=d[x]+1||fabs(v)<eps) continue;
        if(fabs(flow=dfs(y,min(res,v)))>=eps)
        {
            res-=flow;
            e[i].v-=flow;
            e[i^1].v+=flow;
            if(fabs(res)<eps) break;
        }
    }
    return lim-res;
}
double dinic()
{
    double flow,ans=0;
    while(bfs())
        while(fabs(flow=dfs(s,inf))>=eps)
            ans+=flow;
    return ans;
}
double check(double x)
{
    edge_cnt=1;
    memset(head,0,sizeof(head));
    for(int i=1;i<=n;++i) add(i+m,t,x);
    for(int i=1;i<=m;++i)
    {
        int x=ed[i].x,y=ed[i].y;
        add(s,i,1.0),add(i,x+m,inf),add(i,y+m,inf);
    }
    return m*1.0-dinic();
}
int work()
{
    int ans=0;
    memset(du,0,sizeof(du));
    memset(vis,0,sizeof(vis));
    check(g);
    for(int i=1;i<=m;++i)
    {
        int x=ed[i].x,y=ed[i].y;
        if(d[i])
        {
            if(++du[x]==1) ans++,vis[x]=true;
            if(++du[y]==1) ans++,vis[y]=true;
        }
    }
    return ans;   
}

......

l=0,r=m,g=0;
while(l+1/((double)n*(double)n)<r)
{
    double mid=(l+r)/2.0;
    if(check(mid)>eps) g=l=mid;
    else r=mid;
}
printf("%d\n",work());
for(int i=1;i<=n;++i)
    if(vis[i])
        printf("%d\n",i);

最小割二元关系

二元关系指如选和不选的关系

建立最小割模型,来解决一系列问题,如happiness文理分科人员雇佣

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!