GDKOI2014 石油储备计划
首先容易得到结论:达到平衡状态时,每个点的油量要么为(sum/n),要么为(sum/n)+1,现在每个点有一个初始容量,在树上相邻的点可以转移石油。
我们可以据此建模
对于s向每个点,连一条上下界均为初始量的边,费用为0,表示最初容量,
对于每个点向t,连一条下界为(sum/n),上界为(sum/n)+1的边,表示经转移后的容量
对于原树上相邻的点,连一条下界为0,上界为正无穷,费用为距离的边,表示转移可用的路径
然后跑一遍最小费用可行流即可
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> #include<cstring> using namespace std; typedef long long ll; int g[2001],next[2011],y[2011],cost[2011],flow[2011]; int que[2000011],dis[2011],lb[2011],mn[2011],vis[2011]; int du[2011],a[2011]; int tt,tl,n,i,s,t,S,T,x,z,q,sum,dt,tj; ll ans; void star(int i,int j,int k,int l) { tt++; next[tt]=g[i]; g[i]=tt; y[tt]=j; flow[tt]=k; cost[tt]=l; tt++; next[tt]=g[j]; g[j]=tt; y[tt]=i; flow[tt]=0; cost[tt]=-l; } void Spfa()//记录dis(最短路),min(最小流量),lb(连过的边,退流用) { int l,r,x,j,k; memset(dis,127,sizeof(dis)); memset(mn,0,sizeof(mn)); memset(lb,0,sizeof(lb)); tl++; l=r=1; que[l]=S; dis[S]=0;mn[S]=21474836; vis[S]=tl; while(l<=r){ x=que[l]; j=g[x]; while(j!=0){ if(flow[j]>0){ k=y[j]; if(dis[x]+cost[j]<dis[k]){ dis[k]=dis[x]+cost[j]; if(mn[x]<flow[j])mn[k]=mn[x]; else mn[k]=flow[j]; lb[k]=j; if(vis[k]!=tl){ r++; que[r]=k; vis[k]=tl; } } } j=next[j]; } l++; vis[x]--; } } void Minflow() { int x,j; ans=0; while(true){ Spfa(); if(dis[T]==2139062143)break; ans+=(ll)mn[T]*dis[T]; x=T; while(x!=S){ j=lb[x]; flow[j]-=mn[T]; flow[j^1]+=mn[T]; x=y[j^1]; } } } int main() { scanf("%d",&dt); for(tj=1;tj<=dt;tj++){ tt=1; memset(g,0,sizeof(g)); memset(du,0,sizeof(du)); scanf("%d",&n); sum=0; s=n+1;t=n+2;S=n+3;T=n+4; for(i=1;i<=n;i++){ scanf("%d",&a[i]); du[i]+=a[i]; du[s]-=a[i]; sum+=a[i]; } for(i=1;i<=n;i++){ du[i]-=(sum/n); du[t]+=(sum/n); star(i,t,1,0); } for(i=1;i<n;i++){ scanf("%d%d%d",&x,&z,&q); star(x,z,21474836,q); star(z,x,21474836,q); } star(t,s,21474836,0); for(i=1;i<=t;i++){ if(du[i]>0)star(S,i,du[i],0); else star(i,T,-du[i],0); } Minflow(); printf("%lld\n",ans); } }