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; }