负载平衡问题:
现有n个仓库,初始状态下每个仓库中有一些货物,且各仓库中数量不等。仓库只能向相邻的仓库搬运货物。现在想要让各个仓库的货物都相等,请求出最小的搬运量。输入n,初始状况下每个仓库的货物量,输出最少搬运量(输入保证初始货物量的平均数为整数)。
这题有许多解法,比如说贪心。不过它是网络流24题,网络流也可以解。
这道题要求一个最小的值,想到费用流。题解里有许多很玄妙的建图方法,不过最暴力的建图莫过于:
设初始时仓库i中货物量为starti,它们的平均数为aver。每个仓库建一个点,另外建立源点S和汇点T。对于仓库i,首先从S连接一条容量为starti,费用为0的边,表示初始时这个仓库有这么多货物可以调用;再向T连接一条容量为aver,费用为0的边,表示最后这个仓库应该有这么多货物。这些都不算作搬运,因此费用都为0。
接下来处理仓库间的边。由于我们是暴力做法,因此按题意,每个仓库向相邻仓库各建一条容量为+∞,费用为1的边,表示这个仓库可以向两侧输送任意的货物,但这种搬运计入总代价,所以费用为1。写代码的时候可以把+∞改为货物总量。
由于最大流已经确定为货物总量(因为连向T的边只有n条容量为aver的边,S的出边的容量和也为货物总量),因此可以在这张图上跑费用流,最小费用就是题目的解。这种方法虽然比较直白,但是也是可以AC的,十分具有灵性~
仓库间建图的方法可以改进一下:显然过多的搬运是不必要的(即不需要先把一个仓库搬到aver以下,再把它调整回aver),但是可能会有中间转运(比如其它仓库已经达到aver,只剩下两个仓库,一个多一个少,但它们又不相邻,显然需要经过中间仓库来转运),因此可以从每个初始值大于aver的仓库向每个初始值小于aver的仓库连边,容量依然是+∞,费用为它们之间的距离(即中间经过的转运仓库数+1)来模拟中间转运的过程。
这样建图依然跑费用流即可。
对于n≤100的数据来讲,两种建图没什么差别~
#include<cstdio> #include<cstring> #define min(a,b) (a<b?a:b) #define MXN 100+2 #define MXM 500+5 int n,cost_ans,flow_ans; int start[MXN]; int aver; int v[2*MXM],flow[2*MXM],cost[2*MXM],fst[MXN],nxt[2*MXM]; int dis[MXN],prev[MXN],prev_edge[MXN]; bool vis[MXN]; int etop; int S,T; int queue[MXN*MXN],head,tail; void AddEdge(int x,int y,int z,int w){ v[etop]=y; flow[etop]=z; cost[etop]=w; nxt[etop]=fst[x]; fst[x]=etop++; v[etop]=x; flow[etop]=0; cost[etop]=-w; nxt[etop]=fst[y]; fst[y]=etop++; return; } bool EKSPFA(){ memset(vis,0,sizeof(vis)); memset(dis,0x7f,sizeof(dis)); int x,y; head=tail=0; queue[tail++]=S; dis[S]=0; vis[S]=true; while(head<tail){ x=queue[head++]; for(y=fst[x];y!=-1;y=nxt[y]){ if(flow[y]>0 && dis[v[y]]>dis[x]+cost[y]){ if(vis[v[y]]==false){ vis[v[y]]=true; queue[tail++]=v[y]; } dis[v[y]]=dis[x]+cost[y]; prev[v[y]]=x; prev_edge[v[y]]=y; } } vis[x]=false; } if(dis[T]==0x7f7f7f7f) return false; else return true; } void MinCostFlow(){ cost_ans=flow_ans=0; int f; while(EKSPFA()){ f=0x6fffffff; for(int i=T;i!=S;i=prev[i]){ f=min(f,flow[prev_edge[i]]); } flow_ans+=f; cost_ans+=f*dis[T]; for(int i=T;i!=S;i=prev[i]){ flow[prev_edge[i]]-=f; flow[prev_edge[i]^1]+=f; } } return; } int Next(int x){ if(x+1<=n) return x+1; else return 1; } int Prev(int x){ if(x-1>0) return x-1; else return n; } int main(){ memset(fst,-1,sizeof(fst)); memset(nxt,-1,sizeof(nxt)); scanf("%d",&n); S=0,T=n+1; etop=aver=0; for(int i=1;i<=n;i++){ scanf("%d",&start[i]); AddEdge(S,i,start[i],0); aver+=start[i]; } aver/=n; for(int i=1;i<=n;i++){ AddEdge(i,T,aver,0); AddEdge(i,Next(i),aver*n,1); AddEdge(i,Prev(i),aver*n,1); } MinCostFlow(); printf("%d ",cost_ans); return 0; }