Description
CZ 市为了欢迎全国各地的同学,特地举办了一场盛大的美食节。
作为一个喜欢尝鲜的美食客,小 M 自然不愿意错过这场盛宴。他很快就尝遍了美食节所有的美食。
然而,尝鲜的欲望是难以满足的。尽管所有的菜品都很可口,厨师做菜的速度也很快,小 M 仍然觉得自己桌上没有已经摆在别人餐桌上的美食是一件无法忍受的事情。
于是小 M 开始研究起了做菜顺序的问题,即安排一个做菜的顺序使得同学们的等待时间最短。
小 M 发现,美食节共有n种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。
总共有m个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。
然后每个厨师就会同时开始做菜。厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。
此外,小 M 还发现了另一件有意思的事情——虽然这m个厨师都会制作全部的n种菜品,但对于同一菜品,不同厨师的制作时间未必相同。
他将菜品依次编号,厨师依次编号,将第i个厨师制作第j种菜品的时间记为tij。
小 M 认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。
换句话说,如果一个同学点的菜是某个厨师做的第i道菜,则他的等待时间就是这个厨师制作i-1道菜的时间之和。而总等待时间为所有同学的等待时间之和。
现在,小 M 找到了所有同学的点菜信息——有i个同学点了第j种菜品。他想知道的是最小的总等待时间是多少。
n<=40,m<=100,点菜总人数<=800
这么小的数据范围,肯定是网络流啊。。。
但是仔细一看貌似也不小,那个800是个什么玩意?
这题既然要看总时间,那么我们就去考虑每个人的贡献。
你对总时间的贡献就是你的菜的时间×(你后面的人数+1)。那个1是你自己。
所以我们把厨师拆成多个点表示这个厨师做的倒数第i道菜,流量限制为1。
那么第k种菜向第j个厨师做的倒数第i道菜连的边的费用就是i*t[k][j]。
然后就跑一个费用流计算总费用就好了。
一个最简单而暴力的思路就是把点完全拆开建上所有边。
1 #include<cstdio> 2 int n,m,x[450],num[1005][8005],t[450][8005],cn,tot,ans; 3 int fir[222222],l[20000005],to[20000005],c[20000005],v[20000005],cnt=1; 4 int iq[222222],q[222222],dt[222222],pre[222222]; 5 void link(int a,int b,int w,int C){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;v[cnt]=w;c[cnt]=C;} 6 bool SPFA(){ 7 for(int i=1;i<=cn;++i)dt[i]=1234567890; 8 for(int h=1,t=1;h<=t;++h,iq[q[h]]=0)for(int i=fir[q[h]];i;i=l[i])if(dt[to[i]]>dt[q[h]]+c[i]&&v[i]){ 9 dt[to[i]]=dt[q[h]]+c[i];pre[to[i]]=i; 10 if(!iq[to[i]])q[++t]=to[i],iq[to[i]]=1; 11 } 12 return dt[cn]!=1234567890; 13 } 14 int main(){ 15 scanf("%d%d",&n,&m);cn=n; 16 for(int i=1;i<=n;++i)scanf("%d",&x[i]),link(0,i,x[i],0),link(i,0,0,0),tot+=x[i]; 17 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&t[i][j]); 18 for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k)num[j][k]=++cn; 19 cn++; 20 for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k)link(num[j][k],cn,1,0),link(cn,num[j][k],0,0); 21 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k) 22 link(i,num[j][k],1,t[i][j]*k),link(num[j][k],i,0,-t[i][j]*k); 23 while(SPFA())for(int i=pre[cn];i;i=pre[to[i^1]])v[i]--,v[i^1]++,ans+=c[i]; 24 printf("%d\n",ans); 25 }
但是稍算一下,你建了800*100个点,40*100*800条变,跑了800遍SPFA。
肯定是无法接受的。
但其实,你让某一个厨师做倒数第二道菜的前提是他已经有倒数第一道菜可做了,不然倒数第一个会比倒数第二个优。
那么就按照这个思路进行优化,如果某一个厨师做了这道菜,那么再把他做的下一道菜的点和边建出来。
数组开大一点。
1 #include<cstdio> 2 int n,m,x[450],num[1005][8005],t[450][8005],cn,ans,tot; 3 int fir[222222],l[20000005],to[20000005],c[20000005],v[20000005],tt[20000005],cnt=1; 4 int iq[222222],q[222222],dt[222222],pre[222222],al[1005][8005]; 5 void link(int a,int b,int w,int C,int T){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;v[cnt]=w;c[cnt]=C;tt[cnt]=T;} 6 bool SPFA(){ 7 for(int i=1;i<=cn;++i)dt[i]=1234567890; 8 for(int h=1,t=1;h<=t;++h,iq[q[h]]=0)for(int i=fir[q[h]];i;i=l[i])if(dt[to[i]]>dt[q[h]]+c[i]&&v[i]){ 9 dt[to[i]]=dt[q[h]]+c[i];pre[to[i]]=i; 10 if(!iq[to[i]])q[++t]=to[i],iq[to[i]]=1; 11 } 12 return dt[cn]!=1234567890; 13 } 14 int main(){ 15 scanf("%d%d",&n,&m);cn=n; 16 for(int i=1;i<=n;++i)scanf("%d",&x[i]),link(0,i,x[i],0,0),link(i,0,0,0,0),tot+=x[i]; 17 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&t[i][j]); 18 for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k)num[j][k]=++cn; 19 cn++; 20 for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k)link(num[j][k],cn,1,0,0),link(cn,num[j][k],0,0,0); 21 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int k=1;k<=1;++k) 22 link(i,num[j][k],1,t[i][j]*k,1),link(num[j][k],i,0,-t[i][j]*k,1); 23 while(SPFA()){ 24 for(int i=pre[cn];i;i=pre[to[i^1]]){ 25 v[i]--,v[i^1]++,ans+=c[i]; 26 int x=to[i],y=to[i^1],a; 27 if(!tt[i])continue; 28 if(x<y)continue;else x^=y^=x^=y; 29 for(int j=1;j<=m;++j)if(num[j][tt[i]]==y)a=j; 30 if(!al[a][tt[i]+1])for(int j=1;j<=n;++j)link(j,num[a][tt[i]+1],1,t[j][a]*(tt[i]+1),tt[i]+1),link(num[a][tt[i]+1],j,0,-t[j][a]*(tt[i]+1),tt[i]+1); 31 al[a][tt[i]+1]=1; 32 } 33 } 34 printf("%d\n",ans); 35 }