[JZOJ3370]【NOI2012】美食节

Description

CZ 市为了欢迎全国各地的同学,特地举办了一场盛大的美食节。

作为一个喜欢尝鲜的美食客,小 M 自然不愿意错过这场盛宴。他很快就尝遍了美食节所有的美食。然而, 尝鲜的欲望是难以满足的。尽管所有的菜品都很可口,厨师做菜的速度也很快,小 M 仍然觉得自己桌上没有已经摆在别人餐桌上的美食是一件无法忍受的事情。于是小 M 开始研究起了做菜顺序的问题,即安排一个做菜的顺序使得同学们的等待时间最短。

小 M 发现,美食节共有 n 种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。总共有 m 个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。然后每个厨师就会同时开始做菜。 厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。

此外,小 M 还发现了另一件有意思的事情——虽然这 m 个厨师都会制作全部的 n 种菜品,但对于同一菜品,不同厨师的制作时间未必相同。他将菜品用1,2, … , n 依次编号,厨师用 1,2, … , m 依次编号,将第 j 个厨师制作第 i 种菜品的时间记为 Ti,j 。

小 M 认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。换句话说,如果一个同学点的菜是某个厨师做的第 k 道菜,则他的等待时间就是这个厨师制作前 k 道菜的时间之和。 而总等待时间为所有同学的等待时间之和。

现在,小 M 找到了所有同学的点菜信息——有 pi 个同学点了第 i 种菜品(i = 1,2, …, n) 。他想知道的是最小的总等待时间是多少。
这里写图片描述

Solution

这种题,感觉就感觉的出来是费用流嘛

对于每一个厨师我们拆点,分别代表他做第几道菜。

但是我们发现,如果是直接代表做第几道菜,因为不知道后面还要做几道,因此不能知道它对后面的影响。

不妨每个点代表做倒数第几道菜,那么每个点后面做了几个菜是已知的。

S向每道菜连容量为人数,费用为0的边
菜向每个厨师倒数第K道菜连容量为1,费用为厨师做这个菜的时间*K(因为它后面还有K-1道菜等待)

每个厨师的每道菜又向T连边,容量为1,费用为0。

但是边数很大
我们可以边跑边连边

具体而言,每新增广出来一个厨师的一道菜,就新建他的下一道菜,相应连边。

可以通过这道题。

Code

#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 1005
using namespace std;
int d[N*N],ed,dis[N],f[N][N],c[N][N],a[N][N],n,m,n1,ct[N][N],p[N][2],fr[N],ans;
bool bz[N];
bool spfa()
{
    int l=0,r=1;
    memset(dis,107,sizeof(dis));
    memset(fr,0,sizeof(fr));
    dis[1]=0,bz[1]=1;
    d[1]=1;
    while(l<r)
    {
        int k=d[++l];
        fo(i,1,a[k][0])
        {
            int p=a[k][i];
            if(f[k][p]&&dis[p]>dis[k]+c[k][p])
            {
                dis[p]=dis[k]+c[k][p],fr[p]=k;
                if(!bz[p]) bz[p]=1,d[++r]=p;
            }
        }
        bz[k]=0;
    }
    return (dis[ed]!=1802201963);
}
void link(int x,int y,int v,int s)
{
    f[x][y]=v;
    c[x][y]=s,c[y][x]=-s;
    a[x][++a[x][0]]=y,a[y][++a[y][0]]=x;
}
void get()
{
    int k=ed,v=1802201963;
    while(fr[k]) v=min(f[fr[k]][k],v),k=fr[k];
    k=ed;
    while(fr[k]) ans+=v*c[fr[k]][k],f[fr[k]][k]-=v,f[k][fr[k]]+=v,k=fr[k];
    fo(i,1,m)
    {
        if(f[p[i][0]][ed]==0)
        {
            p[i][0]=++n1,p[i][1]++;
            link(n1,ed,1,0);
            fo(j,1,n) link(j+1,n1,1,ct[i][j]*p[i][1]);
        }
    }
}
int main()
{
    cin>>n>>m;
    n1=1;
    fo(i,1,n)
    {
        int x;
        scanf("%d",&x);
        link(1,++n1,x,0);
    }
    fo(i,1,m) p[i][0]=++n1,p[i][1]=1;
    n1++;
    ed=n1;
    fo(i,1,m) link(p[i][0],n1,1,0);
    fo(i,1,n)
    {
        fo(j,1,m)
        {
            scanf("%d",&ct[j][i]);
            link(i+1,p[j][0],1,ct[j][i]);
        }
    }
    ans=0;
    while(spfa()) get();
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值