uva 10594 Data Flow

最小费用最大流:无向图(所以必须用邻接表)+当流量为D时的最小费用(即限制流量在D,求最大流)

题意:就是给你一个无向图,给出所有的无向边和它们的单位费用(题中说了不会有平行边),然后给出D,K,K是指每条无向边的容量固定为K,然后问当流量为D时最小费用是多少,如果流量无法得到D这个数值,那么就输出Impossible.  解决这个问题的方法就是限制流量,加一个新的源点(或者汇点,道理一样),增加一条有向边,就是新的源点指向原本的源点,这条有向边的容量就是D,费用是0,然后求新源点到汇点的最小费用最大流,可以知道,求出来的最大流一定是小于等于D的(同样是最大水流取决于最细水管的道理,水流必须从新的源点出发,必须而且只能经过新源点指向原本源点的那条有向边,所以最大流量只能小于等于这条有向边的容量D)

然后就是邻接表建图的问题

对于最大流问题,没有存在的有向边我们用cap[u][v]=0表示容量为0,而最小费用问题中即便不存在的边我们也要用cost[v][u]=-cost[u][v]表示取反的费用,所以最小费中一个有向边要变为两条来处理,所以一个无向边要变成两个有向边,再变两条,也就是一条无向边变为四条有向边处理

 

然后就是白书中介绍的求最小费用最大流问题的模板   SPFA+增广   不过要注意邻接表和邻接矩阵记录路径的方法不同,而且增广时也有区别,具体看代码

 

 

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const long long INF=1000000000000000000;
#define N 110
#define M 5010
struct edge
{
    int u,v,next;
    long long cost,flow;
}e[M*4];
int n,m,s,t;
long long D,K,F,C;
long long d[N];
int edgenum;   //最终边集数组里面的边的条数
int first[N],p[N];
void add(int u , int v ,long long c , long long f)
{
    e[edgenum].u=u;
    e[edgenum].v=v;
    e[edgenum].cost=c;
    e[edgenum].flow=f;
    e[edgenum].next=first[u];
    first[u]=edgenum;
    edgenum++;  //边集数组当前的下标
}

void print_graph()
{
    for(int i=0; i<=n; i++)
    {
        printf("%d:_______________\n",i);
        for(int k=first[i]; k!=-1; k=e[k].next)
            printf("%d\\%lld\\%lld\n",e[k].v,e[k].cost,e[k].flow);
    }
    printf("**********\n");
    return ;
}
void SPFA()
{
    queue <int> q;
    int vis[N];
    for(int i=0; i<=n; i++) 
    d[i]=INF; 
    memset(vis,0,sizeof(vis));
    memset(p,-1,sizeof(p)); 
    d[s]=0; vis[s]=1;
    q.push(s);

    while(!q.empty())
    {
        int u=q.front(); vis[u]=0; q.pop();
        for(int k=first[u]; k!=-1; k=e[k].next) //遍历点u的邻接表
        {
            int v=e[k].v;
            long long c=e[k].cost , f=e[k].flow;
            if( f && d[u]+c < d[v])
            {
                p[v]=k; 
                //邻接表的记录方法是记录点v但是所对应的边k,就可以知道该边的所有信息了
                d[v]=d[u]+c;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return ;
}
int mincost_maxflow()
{
    F=C=0;
    while(1)
    {
        SPFA();
        if(d[t]==INF)  break;
        
        long long a=INF;
        for(int k=p[t]; k!=-1; k=p[e[k].u]) //这里的k是指第k条边,不再是邻接矩阵里面的顶点
            if(e[k].flow<a) a=e[k].flow;
        for(int k=p[t]; k!=-1; k=p[e[k].u]) //增广,这里的k是指第k条边,不再是邻接矩阵里面的顶点
        {
            e[k].flow-=a;  
            e[k^1].flow+=a;  //因为之前的add函数的排列顺序才能这样使用位运算
        }

        F+=a;
        C+=a*d[t];
        //printf("a=%lld  d[t]=%lld  F=%lld   C=%lld\n",a,d[t],F,C);
    }
    //printf("F=%lld   C=%lld\n",F,C);
    return F==D?C:-1;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int uu[M],vv[M]; long long cc[M];
        for(int i=1; i<=m; i++)  //先临时保存无向边
            scanf("%d%d%lld",&uu[i],&vv[i],&cc[i]);
        scanf("%lld%lld",&D,&K);
        edgenum=0; //为方便后面的位运算,边集数组要从下标0开始保存
        memset(first,-1,sizeof(first));  //所以first数组初始化为-1
        for(int i=1; i<=m; i++)  //处理所有的无向边
        {//下面4个add函数里面的参数这样排列是必要的,不要搞乱
            add(uu[i] , vv[i] , cc[i] , K);  //有向边<u,v>,费用c,容量K
            add(vv[i] , uu[i] , -cc[i] , 0);  //其反向,费用取反,容量为0
            add(vv[i] , uu[i] , cc[i] , K);  //有向边<v,u>,费用c,容量K
            add(uu[i] , vv[i] , -cc[i] , 0);  //其反向,费用取反,容量为0
        }
        add(0,1,0,D);
        add(1,0,0,0);
        //最后要加上一个有向边<0,1>,和它的反向,即先添加的一个源点
        //容量限制为D,费用为0
        //print_graph();  //测试函数
        s=0;  t=n;
        mincost_maxflow();
        if(F==D) //最小费用最大流
            printf("%lld\n",C);
        else
            printf("Impossible.\n");
    }
    return 0;
}

 

 

 

 

posted @ 2012-11-28 23:28  Titanium  阅读(691)  评论(0编辑  收藏  举报