学习笔记:费用流

概念

费用流,全称最小费用最大流。是网络流中的一类题型。和最大流的区别就是每条边加了一个单位费用,每流 1 1 1的流量就要花费 m o n e y e money_e moneye。要求的东西也变成了在最大流中费用最小的一个。
残留网络:在费用流中,残留网络还需要建立反向边的费用为 − m o n e y e -money_e moneye,因为退货肯定要退钱。

算法

EK算法

方法

把求最大流 E K EK EK算法中的 b f s bfs bfs换成最短路即可。因为残留网络中有负权边,所以 d i j dij dij会出问题,最好用 s p f a spfa spfa。加上一个极大值可以让 d i j dij dij都变成正权,这个技巧有点像最大密度子图,详见我的博客最小割。但是一般 s p f a spfa spfa足够了,所以这里只介绍 s p f a spfa spfa的方法。不知道 E K EK EK怎么做的可以看我的博客最大流。然后我们继续来看,我们可以求出一条增广路的费用和以及流量。因为这一条路径每一条边流量一定相等(少了流不过去,多了又不流量守恒),则设增广路中边集为 E E E,增广路路径之和为 M o n e y Money Money,所以路径的费用 = ∑ e ∈ E ( m o n e y e × f l o w ) =\displaystyle\sum_{e\in E}(money_e\times flow) =eE(moneye×flow) = ∑ e ∈ E ( m o n e y e ) × f l o w =\displaystyle\sum_{e\in E}(money_e)\times flow =eE(moneye)×flow = M o n e y × f l o w =Money\times flow =Money×flow那么,每次增广路的费用加上一个 M o n e y × f l o w Money\times flow Money×flow即可。
时间复杂度 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)

例题

AcWing 2174

这个题套板子即可。

#include<bits/stdc++.h>
using namespace std;
const int NN=5004,MM=100004;
int head[NN],ne[MM],e[MM],w[MM],c[MM],f[NN],d[NN],pre[NN],idx=-1,flow,cost,s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
void EK()
{
    while(spfa())
    {
        flow+=f[t];
        cost+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
}
int main()
{
    memset(head,-1,sizeof(head));
    int n,m;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++)
    {
        int u,v,l,money;
        scanf("%d%d%d%d",&u,&v,&l,&money);
        add(u,v,l,money);
    }
    EK();
    printf("%d %d",flow,cost);
    return 0;
}

费用流解二分图最大权匹配

概念

二分图最大权匹配,就是二分图匹配加上了边权,要求一个边权最大的匹配。

方法

还是最大流解二分图的建图方式,两点之间的边加上费用即可。因为是求最大权,所以边权变成负的求最小费用最大流,最后再把 E K EK EK的结果变回正的即可。不知道怎么最大流解二分图的,见我的博客最大流

例题

AcWing 2193

这个题目就是一个二分图最大权匹配问题。但是又要求一个最小权匹配,直接就正权求最小费用最大流即可。

#include<bits/stdc++.h>
using namespace std;
const int NN=104,MM=5204;
int head[NN],ne[MM],e[MM],w[MM],c[MM],f[NN],d[NN],pre[NN],idx=-1,s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    memset(head,-1,sizeof(head));
    int n;
    scanf("%d",&n);
    t=2*n+1;
    for(int i=1;i<=n;i++)
    {
        add(s,i,1,0);
        add(i+n,t,1,0);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            int x;
            scanf("%d",&x);
            add(i,j+n,1,x);
        }
    printf("%d\n",EK());
    for(int i=0;i<=idx;i+=2)
    {
        c[i]+=c[i^1];
        c[i^1]=0;
        w[i]=-w[i];
        w[i^1]=-w[i^1];
    }
    printf("%d",-EK());
    return 0;
}

技巧:拆点

概念

不知道怎么拆点的,见我的博客最大流。这里和最大流的拆点一样,只不过边加上了费用而已。在费用流中还有一种情况要拆点,就是有点权,拆点之间边的费用就是点权。

方法

和最大流的一样。

例题

AcWing 2191

这个题有点权,要拆点。第一问就是点只能用一次的情况,拆点限制为 1 1 1即可。第二问就是每个点用多次,把入点到出点的限制放开即可。第三问就是边的限制放开。

#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=3124;
int g[20][40],id[20][40],head[NN],ne[MM],w[MM],c[MM],e[MM],d[NN],f[NN],pre[NN],idx,s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    c[idx]=0;
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    int n,m,cnt=0;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i+m-1;j++)
        {
            scanf("%d",&g[i][j]);
            id[i][j]=++cnt;
        }
    t=2*cnt+2;
    memset(head,-1,sizeof(head));
    idx=-1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i+m-1;j++)
        {
            add(id[i][j]*2,id[i][j]*2+1,1,-g[i][j]);
            if(i==1)
                add(s,id[i][j]*2,1,0);
            if(i==n)
                add(id[i][j]*2+1,t,1,0);
            if(i<n)
            {
                add(id[i][j]*2+1,id[i+1][j]*2,1,0);
                add(id[i][j]*2+1,id[i+1][j+1]*2,1,0);
            }
        }
    printf("%d\n",-EK());
    memset(head,-1,sizeof(head));
    idx=-1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i+m-1;j++)
        {
            add(id[i][j]*2,id[i][j]*2+1,1e9,-g[i][j]);
            if(i==1)
                add(s,id[i][j]*2,1,0);
            if(i==n)
                add(id[i][j]*2+1,t,1e9,0);
            if(i<n)
            {
                add(id[i][j]*2+1,id[i+1][j]*2,1,0);
                add(id[i][j]*2+1,id[i+1][j+1]*2,1,0);
            }
        }
    printf("%d\n",-EK());
    memset(head,-1,sizeof(head));
    idx=-1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i+m-1;j++)
        {
            add(id[i][j]*2,id[i][j]*2+1,1e9,-g[i][j]);
            if(i==1)
                add(s,id[i][j]*2,1,0);
            if(i==n)
                add(id[i][j]*2+1,t,1e9,0);
            if(i<n)
            {
                add(id[i][j]*2+1,id[i+1][j]*2,1e9,0);
                add(id[i][j]*2+1,id[i+1][j+1]*2,1e9,0);
            }
        }
    printf("%d\n",-EK());
    return 0;
}

AcWing 382

这个题有点权,需要拆点解决。把一个点拆成入点和出点,费用是价值的容量为 1 1 1,再来一种以后走的边,费用为 0 0 0容量为正无穷,求最大费用最大流。

#include<bits/stdc++.h>
using namespace std;
const int NN=10004,MM=30004;
int n,head[NN],ne[MM],w[MM],c[MM],e[MM],d[NN],f[NN],pre[NN],idx=-1,s,t=1;
bool st[NN];
int get(int x,int y)
{
    return ((x-1)*n+y)*2;
}
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    int k;
    scanf("%d%d",&n,&k);
    memset(head,-1,sizeof(head));
    add(s,get(1,1),k,0);
    add(get(n,n)+1,t,k,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            int x;
            scanf("%d",&x);
            add(get(i,j),get(i,j)+1,1,-x);
            add(get(i,j),get(i,j)+1,1e9,0);
            if(i<n)
                add(get(i,j)+1,get(i+1,j),1e9,0);
            if(j<n)
                add(get(i,j)+1,get(i,j+1),1e9,0);
        }
    printf("%d\n",-EK());
    return 0;
}

AcWing 2184

这个题就是典型的多种状态。把每天的毛巾拆成两个点,一个是洗过,一个是没有洗。每天没洗过的毛巾可以流到下一天,也可以经过清洗流到干净的毛巾,快洗流到新毛巾的 i + t 1 i+t1 i+t1天,费用 m 1 m1 m1,慢洗同理。也可以购买新毛巾,费用是 m 0 m0 m0,新毛巾用了可以流到汇点。每天都会用完刚好 x x x条毛巾,所以会有 x x x条脏毛巾,但是他们流向汇点计算答案了,所以这里就改成从源点向脏毛巾的点流 x x x

#include<bits/stdc++.h>
using namespace std;
const int NN=2004,MM=20004;
int head[NN],e[MM],ne[MM],c[MM],w[MM],idx=-1,f[NN],d[NN],pre[NN],s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                d[v]=d[u]+w[i];
                f[v]=min(f[u],c[i]);
                pre[v]=i;
                if(!st[v])
                {
                    st[v]=true;
                    q.push(v);
                }
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    int n,m0,t1,m1,t2,m2;
    scanf("%d%d%d%d%d%d",&n,&m0,&t1,&m1,&t2,&m2);
    t=2*n+1;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        add(s,i,x,0);
        add(i+n,t,x,0);
        add(s,i+n,1e9,m0);
        if(i<n)
            add(i,i+1,1e9,0);
        if(i+t1<=n)
            add(i,n+i+t1,1e9,m1);
        if(i+t2<=n)
            add(i,n+i+t2,1e9,m2);
    }
    printf("%d",EK());
    return 0;
}

上下界可行流

概念

不知道上下界可行流见我的博客最大流,这里就是多了个费用。但是在费用流中要求从源点和流向汇点的边是没有费用的,因为它们只是为了流量守恒。

方法

和最大流的上下界可行流一样。

例题

AcWing 969

这个题就是一个上下界可行流。但是这个是没有上界的,所以上界可以设为正无穷。于是,上界减下界还是正无穷。前一天的志愿者可以到后一天,到了最后一天就流出去。然后我们发现,原图 G G G从志愿者结束工作的时间是流到 t t t了一些人,开始从 s s s流进来了一些开始工作的志愿者,但是新图 G G G的源点和汇点是满足流量守恒的,不能提供志愿者。所以为了流量守恒,结束向开始连一条边即可。如果要公式化的解释我看到了网上一篇解释地特别漂亮的博客。围观点我

#include<bits/stdc++.h>
using namespace std;
const int NN=1004,MM=24004;
int head[NN],ne[MM],w[MM],c[MM],e[MM],d[NN],f[NN],pre[NN],idx=-1,s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
    t=n+2;
    int last=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        if(last>x)
            add(s,i,last-x,0);
        else
            add(i,t,x-last,0);
        add(i,i+1,1e9,0);
        last=x;
    }
    add(s,n+1,last,0);
    for(int i=1;i<=m;i++)
    {
        int b,e,money;
        scanf("%d%d%d",&b,&e,&money);
        add(e+1,b,1e9,money);
    }
    printf("%d\n",EK());
    return 0;
}

费用流应用

AcWing 2192

每个仓库有货物,从源点连容量为货物数量,费用为 0 0 0的边。每个店铺要货物,从店铺向汇点连容量为店铺需求货物的数量,费用为 0 0 0的边。因为保证货物供需平衡,所以不用判断是否满流。再从仓库到店铺连流量为正无穷,费用为运输费用的边即可。

#include<bits/stdc++.h>
using namespace std;
const int NN=154,MM=10004;
int head[NN],ne[MM],e[MM],w[MM],c[MM],f[NN],d[NN],pre[NN],idx=-1,s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    memset(head,-1,sizeof(head));
    int n,m;
    scanf("%d%d",&n,&m);
    t=n+m+1;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        add(s,i,x,0);
    }
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        add(i+n,t,x,0);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            add(i,j+n,1e9,x);
        }
    printf("%d\n",EK());
    for(int i=0;i<=idx;i+=2)
    {
        c[i]+=c[i^1];
        c[i^1]=0;
        w[i]=-w[i];
        w[i^1]=-w[i^1];
    }
    printf("%d",-EK());
    return 0;
}

AcWing 2194

可以先人为地给每个仓库减去平均值,然后多出来的由源点给,少的最后要流到汇点。因为保证有解,所以不用判断是否满流。最后给相邻两个仓库连上费用为 1 1 1容量正无穷的边即可。

#include<bits/stdc++.h>
using namespace std;
const int NN=104,MM=604;
int head[NN],ne[MM],e[MM],w[MM],c[MM],f[NN],d[NN],pre[NN],a[NN],idx=-1,s,t;
bool st[NN];
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    memset(head,-1,sizeof(head));
    int n,sum=0;
    scanf("%d",&n);
    t=n+1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    sum/=n;
    for(int i=1;i<=n;i++)
    {
        a[i]-=sum;
        if(a[i]>0)
            add(s,i,a[i],0);
        else
            add(i,t,-a[i],0);
        add(i,(i-2+n)%n+1,1e9,1);
        add(i,i%n+1,1e9,1);
    }
    printf("%d",EK());
    return 0;
}

AcWing 2195

本题和AcWing 382非常像,因为本题是边权,所以不用拆点。

#include<bits/stdc++.h>
using namespace std;
const int NN=504,MM=5004;
int head[NN],ne[MM],w[MM],c[MM],e[MM],d[NN],f[NN],pre[NN],idx=-1,s,t,n,m;
bool st[NN];
int sum(int x,int y)
{
    return (x-1)*(m+1)+y;
}
void add(int u,int v,int l,int money)
{
    e[++idx]=v;
    ne[idx]=head[u];
    c[idx]=l;
    w[idx]=money;
    head[u]=idx;
    e[++idx]=u;
    ne[idx]=head[v];
    w[idx]=-money;
    head[v]=idx;
}
bool spfa()
{
    memset(f,0,sizeof(f));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(s);
    f[s]=1e9;
    d[s]=0;
    st[s]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        st[u]=false;
        for(int i=head[u];~i;i=ne[i])
        {
            int v=e[i];
            if(c[i]&&d[v]>d[u]+w[i])
            {
                f[v]=min(f[u],c[i]);
                d[v]=d[u]+w[i];
                pre[v]=i;
                if(st[v])
                    continue;
                q.push(v);
                st[v]=true;
            }
        }
    }
    return f[t]>0;
}
int EK()
{
    int res=0;
    while(spfa())
    {
        res+=d[t]*f[t];
        for(int i=t;i!=s;i=e[pre[i]^1])
        {
            c[pre[i]]-=f[t];
            c[pre[i]^1]+=f[t];
        }
    }
    return res;
}
int main()
{
    int ss,ts;
    scanf("%d%d%d%d",&ss,&ts,&n,&m);
    memset(head,-1,sizeof(head));
    t=NN-1;
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=m;j++)
        {
            int x;
            scanf("%d",&x);
            add(sum(i,j),sum(i,j+1),1,-x);
            add(sum(i,j),sum(i,j+1),1e9,0);
        }
    for(int i=1;i<=m+1;i++)
        for(int j=1;j<=n;j++)
        {
            int x;
            scanf("%d",&x);
            add(sum(j,i),sum(j+1,i),1,-x);
            add(sum(j,i),sum(j+1,i),1e9,0);
        }
    for(int i=1;i<=ss;i++)
    {
        int k,x,y;
        scanf("%d%d%d",&k,&x,&y);
        add(s,sum(x+1,y+1),k,0);
    }
    for(int i=1;i<=ts;i++)
    {
        int k,x,y;
        scanf("%d%d%d",&k,&x,&y);
        add(sum(x+1,y+1),t,k,0);
    }
    printf("%d\n",-EK());
    return 0;
}
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值