正题
题目链接:https://www.luogu.com.cn/problem/P2050
题目大意
nnn个菜品mmm个厨师,第iii种菜需要pip_ipi份,第iii个人做第jjj道菜需要时间ti,jt_{i,j}ti,j,求最少等待时间和。
解题思路
这题和之前修车很像,数据变大了。
考虑网络流,如果一个厨师总共要做kkk个菜,那么第iii个菜的时间贡献就是(k−i+1)∗t(k-i+1)*t(k−i+1)∗t,反过来看,做倒数第iii道菜的时间贡献就是i∗ti*ti∗t。
也就是如果目前厨师要做kkk道菜,那么在最开头加入一个新菜时需要增加的时间就是(k+1)∗t(k+1)*t(k+1)∗t。
定义点阵(i,j)(i,j)(i,j)表示第iii个厨师做到第jjj道菜,然后和顾客构建二分图,联向第jjj道菜的费用乘上jjj即可,因为是最小费用所以肯定会优先把小费用的边流掉。
这样正确性已经保证,但是空间复杂度显然不行。考虑动态连边,因为肯定会先流小费用,所以当小费用的有流时在动态加入更大费用的一条边即可。
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define p1(x,y) (((x)-1)*K+(y))
#define p2(x) (p1(m,K)+(x))
using namespace std;
const int N=1e5+10;
struct node{int to,next,w,c;
}a[N*70];
int n,m,ans,tot=1,s,t,K;
int e[50][110],k[50];
int ls[N],f[N],mf[N],pre[N],mark[N];
bool v[N];
queue<int> q;
void addl(int x,int y,int w,int c){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;a[tot].w=w;a[tot].c=c;a[++tot].to=x;a[tot].next=ls[y];ls[y]=tot;a[tot].w=0;a[tot].c=-c;return;
}
bool SPFA(){memset(f,0x3f,sizeof(f));f[s]=0;q.push(s);v[s]=1;mf[s]=2147483647;while(!q.empty()){int x=q.front();q.pop();for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(a[i].w&&f[x]+a[i].c<f[y]){f[y]=f[x]+a[i].c;mf[y]=min(mf[x],a[i].w);pre[y]=i;if(!v[y]){v[y]=1;q.push(y);}}}v[x]=0;}return f[t]<=2147483647/3;
}
void updata(){int x=t;while(x!=s){a[pre[x]].w-=mf[t];a[pre[x]^1].w+=mf[t];if(x==t&&(a[pre[x]^1].to%K)>0){int y=a[pre[x]^1].to;int c=y%K+1,pos=mark[y];for(int i=1;i<=n;i++)addl(p2(i),p1(pos,c),1,e[i][pos]*c);addl(p1(pos,c),t,1,0);mark[p1(pos,c)]=pos;}x=a[pre[x]^1].to;}ans+=mf[t]*f[t];
}
void net_flow(){while(SPFA())updata();
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",&k[i]),K+=k[i];for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&e[i][j]);s=p2(n)+1;t=s+1;for(int i=1;i<=n;i++)addl(s,p2(i),k[i],0),mark[p2(i)]=i;for(int i=1;i<=m;i++){for(int j=1;j<=n;j++)addl(p2(j),p1(i,1),1,e[j][i]);addl(p1(i,1),t,1,0);mark[p1(i,1)]=i;}net_flow();printf("%d",ans);
}