[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)。他想知道的是最小的总等待时间是多少。

对于100%的数据,n <= 40, m <= 100, p <= 800, ti,j <= 1000(其中p = ∑pi,即点菜同学的总人数)。

Data Constraint

每组数据的n、m和p值如下:
测试点编号 n m p
1 n = 5 m = 5 p = 10
2 n = 40 m = 1 p = 400
3 n = 40 m = 2 p = 300
4 n = 40 m = 40 p = 40
5 n = 5 m= 40 p = 100
6 n = 10 m = 50 p = 200
7 n = 20 m = 60 p = 400
8 n = 40 m = 80 p = 600
9 n = 40 m = 100 p = 800
10 n = 40 m = 100 p = 800

分析

很显然是费用流模型,然后考虑构图。

首先是每种菜,从源点向每种菜连一条容量为p[i],费用为0的边。
接下来考虑每个厨师。
因为有先后顺序的影响,考虑对于一个厨师i,他做的菜依次为c1,c2…ck,那么第1个菜给答案的增加量为t[i][c1] * k,第2个菜增加量为t[i][c2] * (k-1)…
然后要建p*m个点,其中点(i,j)表示第i个厨师做的倒数第j个菜。剩下的连边就很显然了。
但是这样会连很多边,会超时。

考虑如何优化,很显然,当一个点(i,j)没有被选时,(i,j+1)是没有用的,所以可以动态地加边。
特判一下m=1的情况。

/**************************************************************
    Problem: 2879
    User: worldwide
    Language: C++
    Result: Accepted
    Time:3208 ms
    Memory:65240 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=81005,maxm=3200005;

typedef long long LL;

int n,tot,m,s,t,calc,p[maxn],h[maxn],e[maxm],next[maxm],fl[maxm],ch[maxm],cost[maxm],f[maxn],now[maxn],visit[maxn],cnt[maxn],T[45][105];

int ans,last;

void add(int x,int y,int flow,int c,int o)
{
    e[++tot]=y; fl[tot]=flow; next[tot]=h[x]; cost[tot]=c; ch[tot]=tot+o; h[x]=tot;
}

bool aug(int x)
{
    visit[x]=calc;
    if (x==t)
    {
        ans+=f[0];
        return 1;
    }
    for (int i=h[x];i;i=next[i])
    {
        if (fl[i]>0 && visit[e[i]]<calc && f[x]==f[e[i]]+cost[i])
        {
            if (aug(e[i]))
            {
                fl[i]--; fl[ch[i]]++;
                if (x>0 && x<=n && !last) last=e[i];
                now[x]=i;
                return 1;
            }
        }
    }
    return now[x]=0;
}

void flow()
{
    memcpy(now,h,sizeof(now));
    for (calc++;aug(0);calc++)
    {
        last=(last-n-1)/s+1;
        cnt[last]++;
        for (int j=1;j<=n;j++)
        {
            add(j,n+(last-1)*s+cnt[last]+1,1,(cnt[last]+1)*T[j][last],1);
            add(n+(last-1)*s+cnt[last]+1,j,0,-(cnt[last]+1)*T[j][last],-1);
        }
        last=0;
    }
}

bool check()
{
    int tmp=1e9;
    for (int i=0;i<=t;i++) if (visit[i]==calc)
        for (int j=h[i];j;j=next[j])
            if (fl[j] && visit[e[j]]<calc && tmp>f[e[j]]+cost[j]-f[i]) tmp=f[e[j]]+cost[j]-f[i];
    if (tmp==1e9) return 0;
    for (int i=0;i<=t;i++) if (visit[i]==calc) f[i]+=tmp;
    return 1;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&p[i]);
        s+=p[i];
        add(0,i,p[i],0,1); add(i,0,0,0,-1);
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=m;j++)
        {
            scanf("%d",&T[i][j]);
            add(i,n+(j-1)*s+1,1,T[i][j],1); add((j-1)*s+1+n,i,0,-T[i][j],-1);
        }
    }
    if (m==1)
    {
        int ans=0;
        for (int i=1;i<=n;i++)
        {
            int j=0;
            for (int k=1;k<=n;k++) if (!j || T[j][1]>T[k][1]) j=k;
            for (int k=1;k<=p[j];k++)
            {
                ans+=s*T[j][1]; s--;
            }
            T[j][1]=1001;
        }
        printf("%d\n",ans);
        return 0;
    }
    t=n+s*m+1;
    for (int j=1;j<=m;j++)
    {
        for (int k=1;k<=s;k++)
        {
            add(n+(j-1)*s+k,t,1,0,1); add(t,n+(j-1)*s+k,0,0,-1);
        }
    }
    for (flow();check();flow());
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值