[BZOJ2055]80人环游世界(有源汇上下界费用流+讲解)

题目:

我是超链接

题解:

关于有源汇上下界的费用流讲解见下方
根据以前的经验,我们知道可以通过拆点限制这个点的流量,然后若是可以转移,就从y[i]->x[j]连边
那么对于一个普通的网络流,本题的图:
s->S[m,m]INF
S->xi[0,INF] 0
xi->yi [vi,vi] 0
yi->xj [0,INF] cost
yi->t [0,INF] 0

修改原图使其成为源汇上下界费用流
首先建立附加源汇ss,tt
对于原图里有的一条边x->y,[l,r],cost,变成x->y,r-l,cost
每一个点的权di定义为所有流入这个点的边的下界和-所有流出这个点的边的下界和
对于一个点i,若di>0,ss->i,di,0;若di<0,i->tt,-di,0
连边t->s,inf,0
然后对ss,tt做最小费用最大流
最终的费用为(网络流中计算的费用+原图中有费用的边的下界*这条边的费用)
那么对于这道题来说,原图有费用的边的下界没有,后面一部分都不用加

这里再强调两点吧:1、无上下界的[0,INF]不用添加入流的上下界 2、容量为0的边直接不用添加啦

代码:

#include 
#include 
#include 
#include 
using namespace std;
#define INF 1e9
const int N=30000;
int tot=-1,nxt[N],point[N],remind[N],c[N],last[N],dis[N],v[N],mincost,d[N];
bool vis[N];
void addline(int x,int y,int cap,int cc)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remind[tot]=cap; c[tot]=cc;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remind[tot]=0; c[tot]=-cc;
}
int addflow(int s,int t)
{
    int now=t,ans=INF;
    while (now!=s)
    {
        ans=min(ans,remind[last[now]]);
        now=v[last[now]^1];
    }
    now=t;
    while (now!=s)
    {
        remind[last[now]]-=ans;
        remind[last[now]^1]+=ans;
        now=v[last[now]^1];
    }
    return ans;
}
bool spfa(int s,int t)
{
    queue<int>q;
    q.push(s);
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    while (!q.empty())
    {
        int x=q.front(); q.pop(); vis[x]=0;
        for (int i=point[x];i!=-1;i=nxt[i])
          if (dis[v[i]]>dis[x]+c[i] && remind[i])
          {
            last[v[i]]=i; dis[v[i]]=dis[x]+c[i];
            if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
          }
    }
    if (dis[t]>INF) return 0;
    int flow=addflow(s,t);
    mincost+=flow*dis[t];
    return 1;
}
int main()
{
    memset(point,-1,sizeof(point));
    int x,n,m;
    scanf("%d%d",&n,&m);
    int s=n*2+1,S=s+1,t=S+1,ss=t+1,tt=ss+1;
    d[s]-=m; d[S]+=m;
    for (int i=1;i<=n;i++) addline(S,i,INF,0),addline(i+n,t,INF,0);
    for (int i=1;i<=n;i++) scanf("%d",&x),d[i]-=x,d[i+n]+=x;
    for (int i=1;ifor (int j=i+1;j<=n;j++)
      {
        scanf("%d",&x);
        if (x!=-1) addline(i+n,j,INF,x);
      }
    for (int i=1;i<=n;i++) addline(i+n,t,INF,0);
    for (int i=1;i<=t;i++)
    { 
        if (d[i]>0) addline(ss,i,d[i],0);
        if (d[i]<0) addline(i,tt,-d[i],0);
    }
    addline(t,s,INF,0);
    while (spfa(ss,tt));
    printf("%d",mincost);
}

普及向:

这个建图其实跟那个有源汇上下界的最大流相似啊
首先建立附加源点ss和附加汇点tt
对于原图中的边x->y,若限制为[b,c],费用为cost,那么连边x->y,流量为c-b,费用为cost
对于原图中的某一个点i,记d(i)为流入这个点的所有边的下界和减去流出这个点的所有边的下界和
若d(i)>0,那么连边ss->i,流量为d(i),费用为0
若d(i)<0,那么连边i->tt,流量为-d(i),费用为0
连边t->s,流量为inf,费用为0

求法:
跑ss->tt的最小费用最大流
答案即为(求出的费用+原图中边的下界*边的费用)

注意:
有上下界的费用流指的是在满足流量限制条件和流量平衡条件的情况下的最小费用流
而不是在满足流量限制条件和流量平衡条件并且满足最大流的情况下的最小费用流
也就是说,有上下界的费用流只需要满足网络流的条件就可以了,而普通的费用流是满足一般条件并且满足是最大流的基础上的最小费用

你可能感兴趣的:(网络流)