题意
有n(<=40)道菜,第i道菜被点了pi(∑p<=800)次;有m(<=100)个人,第i个人做第j道菜用时tij(<=1000),每人同时只能做一道菜,求所有菜需等待时间和的最小值。(这个题意好差啊 还是去看题面吧…题面也不长的说)
背景
今天noip模拟的T3…
算错复杂度,以为很简单,想多路增广,调了2h整,没删windows.h,直接爆零了
题解
脑洞建图神奇优化经典费用流。
前置技能:bzoj1070
我们把每个人拆成p个点,第i道菜的点到第j个人的第k个点满流,代表第j个人要做的倒数第k道菜是i。边的费用是它对这个人之后做的菜的影响,也就是k*cost[i][j]。
然后我们优化一下。显然对于一个人,选择靠前的点要比靠后的更优。初始只连所有菜到每个人的第一个点,对于第i个人的第j个点,如果它满流,再把到所有菜到j+1的边连出来。
于是这个费用流就可以跑得很快了。
代码
写得有点丑…
#include<iostream>
#include<cstdio>
#include<cstring>
#define M 6601000
#define N 80050
#define inf 0x3f3f3f3f
using namespace std;
int n,m,S,T,P,p,ans,ct[41][101],ed[101];
int to[M],hd[M],val[M],cst[M],lk[N],cnt=1;
int pre[N],edg[N],q[N],h,t,dis[N];
inline void add(int u,int v,int w,int c)
{
to[++cnt]=v,hd[cnt]=lk[u],lk[u]=cnt,val[cnt]=w,cst[cnt]=c;
to[++cnt]=u,hd[cnt]=lk[v],lk[v]=cnt,cst[cnt]=-c;
}
bool inq[N];
bool spfa()
{
memset(dis,0x3f,sizeof dis);
h=q[0]=dis[0]=0,t=1;
while(h!=t)
{
inq[p=q[h++]]=0;
if(h==N)h=0;
for(int k,i=lk[p];i;i=hd[i])
if(val[i]&&dis[k=to[i]]>dis[p]+cst[i])
{
dis[k]=dis[p]+cst[i];
pre[k]=p,edg[k]=i;
if(!inq[k])
{
inq[q[t++]=k]=1;
if(t==N)t=0;
}
}
}
return dis[T]<inf;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&p),P+=p,add(S,i,p,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",ct[i]+j),
add(i,n+(j-1)*P+1,1,ct[i][j]);
T=m*P +n+1;
for(int i=1;i<=m;i++)
{
ed[i]=(i-1)*P+n+1;
for(int j=1;j<=P;j++)
add(n+(i-1)*P+j,T,1,0);
}
while(spfa())
{
ans+=dis[T];
for(int i=T;i;i=pre[i])
{
val[edg[i]]--,val[edg[i]^1]++;
p=i-n;
if(i>n&&i<=n+m*P&&pre[i]&&pre[i]<=n&&(i-n)%P&&i==ed[(i-n)/P+1])
{
for(int j=1;j<=n;j++)
add(j,i+1,1,((i-n)%P+1)*ct[j][(i-n)/P+1]);
ed[(i-n)/P+1]=i+1;
}
}
}
printf("%d",ans);
}