此题为2007年四川省选题目
前置知识
最小费用最大流
建议大家看这篇文章: https://baijiahao.baidu.com/s?id=1612179096991409044&wfr=spider&for=pc
思路
我们要使顾客的平均等待时间最小,即我们要使顾客的总等待时间最少
我们先考虑特殊情况,当m==1时,只有一位修车师傅
修第 辆车耗时
若这i辆车按照 顺序被修
那么等待的总时间:
我们再考虑特殊情况,n==3
整理一下式子
可以看出,汽车先被修,对答案的贡献越大
我们再从特殊情况向回推
首先考虑n为任意值,m==1
再考虑m为任意值,n同时也为特殊值
这个时候我们就需要考虑汽车去哪个工人处维修
我用样例举例
2 2 3 2 1 4
我们每位位师傅拆成N个师傅,代表修第j辆车的师傅(注意,第j辆车是当前师傅修的第j辆车)
如下图
红色的点为第一个师傅
绿色的点为第二个师傅
若第i辆车在第j个师傅修车的代价是k,第j个师傅分裂成n个点,车i向分裂的第p个点连代价为p*k的边,容量为1
源点向车连代价为0,容量为1的边
师傅向汇点连代价为0,容量为1的边
跑一遍最小费用最大流即可
为什么这样做是对的?
首先我们跑的是最大流,保证了每辆车都被维修
我们再单独看某个师傅修车
假设某个师傅只负责一俩车,如图
那么车肯定回选择上面的点,因为代价回减少,即使我们的网络流先选择了下面的点,如果上面的点没选,那么此时就有一条增广路,车会重新选择上面的点
而且师傅向汇点连边的容量是1,保证了师傅每个步骤只修1辆车
由此可见,对于一个师傅的分裂点,肯定不会存在1,3点被利用,2号点不被利用的情况,因为1,2点被利用代价更优
我们再回头看我们最开始考虑的特殊情况,m==1时
不就正和这个师傅在网络流中得到的代价一样吗?
就此,我们可以得出,最小费用流跑出的答案就是我们顾客等待总时间的最小值
除以顾客总数,我们就可以得到顾客平均等待的时间最小值
参考代码
#include<bits/stdc++.h> using namespace std; const int inf=101010; int m,n,S,T; int c[20][70]; struct oppo{ int to,s,w,nex; }rod[1000006]; int head[100005],tot; void add(int from,int to,int s,int w) { rod[tot].to=to; rod[tot].s=s; rod[tot].nex=head[from]; rod[tot].w=w; head[from]=tot++; } void link(int from,int to,int s,int w) { add(from,to,s,w); add(to,from,0,-w); } bool flag[100005]; int d[100005]; int cur[100005];//当前弧优化 bool spfa() { queue<int> v; memset(d,10,sizeof(d)); d[S]=0;cur[S]=head[S];v.push(S); while(v.size()){ int lxl=v.front(); v.pop(); for(int i=head[lxl];~i;i=rod[i].nex){ int to=rod[i].to; if(rod[i].s&&d[to]>d[lxl]+rod[i].w){ d[to]=d[lxl]+rod[i].w; cur[to]=head[to]; v.push(to); } } } return d[T]!=d[100000]; } bool vis[100005]; int find(int x,int low) { if(x==T) return low; vis[x]=1; int all=0; for(int i=cur[x];~i;i=rod[i].nex){ cur[x]=i; int to=rod[i].to; if(rod[i].s&&d[to]==d[x]+rod[i].w&&!vis[to]){ int t=find(to,min(rod[i].s,low-all)); if(!t) d[to]=-1; rod[i].s-=t; rod[i^1].s+=t; all+=t; } if(all==low) break; } vis[x]=0; return all; } int dinic() { int ans=0,r; while(spfa()) while(r=find(S,inf)) ans+=r*d[T]; return ans; } int main() { memset(head,-1,sizeof(head)); cin>>m>>n; S=0,T=(m+1)*(n+1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&c[j][i]); //建边 for(int i=1;i<=n;i++){ link(S,i,1,0);//源点连汽车 } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=1;k<=n;k++) link(i,j*n+k,1,c[j][i]*k); } } for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) link(i*n+j,T,1,0); printf("%.2lf",(double)dinic()/n); return 0; }