【题目】
题目描述:
CZ 市为了欢迎全国各地的同学,特地举办了一场盛大的美食节。
作为一个喜欢尝鲜的美食客,小 M 自然不愿意错过这场盛宴。他很快就尝遍了美食节所有的美食。然而,尝鲜的欲望是难以满足的。尽管所有的菜品都很可口,厨师做菜的速度也很快,小 M 仍然觉得自己桌上没有已经摆在别人餐桌上的美食是一件无法忍受的事情。于是小 M 开始研究起了做菜顺序的问题,即安排一个做菜的顺序使得同学们的等待时间最短。
小 M 发现,美食节共有 n n n 种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。总共有 m m m 个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。然后每个厨师就会同时开始做菜。厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。
此外,小 M 还发现了另一件有意思的事情:虽然这 m m m 个厨师都会制作全部的 n n n 种菜品,但对于同一菜品,不同厨师的制作时间未必相同。他将菜品用 1 , 2 , . . . , n 1, 2, ..., n 1,2,...,n 依次编号,厨师用 1 , 2 , . . . , m 1,2,...,m 1,2,...,m 依次编号,将第 j j j 个厨师制作第 i i i 种菜品的时间记为 t i , j t_{i,j} ti,j 。
小 M 认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。换句话说,如果一个同学点的菜是某个厨师做的第 k k k 道菜,则他的等待时间就是这个厨师制作前 k k k 道菜的时间之和。而总等待时间为所有同学的等待时间之和。
现在,小 M 找到了所有同学的点菜信息:有 p i p_i pi 个同学点了第 i i i 种菜品( i = 1 , 2 , . . . , n i=1,2,...,n i=1,2,...,n)。他想知道的是最小的总等待时间是多少。
输入格式:
输入文件的第 1 1 1 行包含两个正整数 n n n 和 m m m ,表示菜品的种数和厨师的数量。
第 2 2 2 行包含 n n n 个正整数,其中第 i i i 个数为 p i p_i pi,表示点第 i i i 种菜品的人数。
接下来有 n n n 行,每行包含 m m m 个非负整数,这 n n n 行中的第 i i i 行的第 j j j 个数为 t i , j t_{i,j} ti,j,表示第 j j j 个厨师制作第 i i i 种菜品所需的时间。
输入每行相邻的两个数之间均由一个空格隔开,行末均没有多余空格。
输出格式:
输出仅一行包含一个整数,为总等待时间的最小值。
样例数据:
输入
3 2
3 1 1
5 7
3 6
8 9
输出
47
备注:
【样例说明】
厨师
1
1
1 先制作
1
1
1 份菜品
2
2
2,再制作
2
2
2 份菜品
1
1
1。点这
3
3
3 道菜的
3
3
3 个同学的等待时间分别为
3
3
3,
3
+
5
=
8
3+5=8
3+5=8,
3
+
5
+
5
=
13
3+5+5=13
3+5+5=13。
厨师
2
2
2 先制作
1
1
1 份菜品
1
1
1,再制作
1
1
1 份菜品
3
3
3。点这
2
2
2 道菜的
2
2
2 个同学的等待时间分别为
7
7
7,
7
+
9
=
16
7+9=16
7+9=16。
总等待时间为
3
+
8
+
13
+
7
+
16
=
47
3+8+13+7+16=47
3+8+13+7+16=47。
虽然菜品
1
1
1 和菜品
3
3
3 由厨师
1
1
1 制作更快,如果这些菜品都由厨师
1
1
1 制作,总等待时间反而更长。如果按上述的做法,将
1
1
1 份菜品
1
1
1 和
1
1
1 份菜品
3
3
3 调整到厨师
2
2
2制作,这样厨师
2
2
2 不会闲着,总等待时间更短。
可以证明,没有更优的点餐方案。
【数据范围】
对于 100 % 100\% 100% 的数据, n ≤ 40 n\le40 n≤40, m ≤ 100 m\le100 m≤100, p ≤ 800 p\le800 p≤800, t i , j ≤ 1000 t_{i,j}\le1000 ti,j≤1000(其中 p = ∑ p i p=∑pi p=∑pi,即点菜同学的总人数)。
每组数据的
n
n
n、
m
m
m 和
p
p
p 值如下:
【分析】
这道题跟修车那道题很像啊,但是有两个不同点:
- 每个菜品可以点多份,这个好办,直接改一下源点到每个菜品的边的容量以及菜品总数即可。
- 数据规模变大了,这时直接建之前的方法建图有超过 6 × 1 0 6 6\times 10^6 6×106条边,会 TLE 的,要想一下优化。
那么,我们就先连现在需要的边,暂时不需要的边就先不要连上。
那什么是 “ “ “暂时不需要的边呢 ” ” ”?
由于我们把每个厨师拆成了 s u m sum sum 个点( s u m sum sum 是菜品总数),现在用 ( i , j ) (i,j) (i,j) 表示厨师 i i i 做在倒数第 j j j 道菜的这个点。
对于厨师 i i i,如果 ( i , j ) (i,j) (i,j) 还没有被增广, ( i , j + 1 ) (i,j+1) (i,j+1) 也就不可能被被增广,因为 ( i , j ) (i,j) (i,j) 肯定比 ( i , j + 1 ) (i,j+1) (i,j+1) 更优。
所以我们动态加边。
在起初,只加上源点到菜品的边、菜品到 ( i , 1 ) (i,1) (i,1) 的边、 ( i , 1 ) (i,1) (i,1) 到汇点的边。
在每次增广后,如果增广到了 ( i , j ) (i,j) (i,j),就把菜品向 ( i , j + 1 ) (i,j+1) (i,j+1) 的边以及 ( i , j + 1 ) (i,j+1) (i,j+1) 的边加上,表示 ( i , j + 1 ) (i,j+1) (i,j+1) 也可供选择了。
然后每次建边后跑最小费用流就可以了。
【代码】
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 6000005
#define inf (1ll<<31ll)-1
using namespace std;
int n,m,s,t,sum,ans,tot=1;
int v[M],w[M],nxt[M],cost[M];
int a[105][105],d[N],first[N],pre[N],flow[N];
bool vis[N];
int id(int i,int j){return (i-1)*sum+j+n;}
void add(int x,int y,int f,int c)
{
nxt[++tot]=first[x];
first[x]=tot,v[tot]=y,w[tot]=f,cost[tot]=c;
}
bool spfa(int s)
{
int x,y,i;
memset(d,0x3f,sizeof(d));
memset(pre,-1,sizeof(pre));
memset(flow,0,sizeof(flow));
memset(vis,false,sizeof(vis));
queue<int>q;q.push(s);d[s]=0,flow[s]=inf;
while(!q.empty())
{
x=q.front();
vis[x]=false;q.pop();
for(i=first[x];i;i=nxt[i])
{
y=v[i];
if(w[i]&&d[y]>d[x]+cost[i])
{
d[y]=d[x]+cost[i];
pre[y]=i,flow[y]=min(flow[x],w[i]);
if(!vis[y]) q.push(y),vis[y]=true;
}
}
}
return d[t]!=0x3f3f3f3f;
}
void dinic()
{
int u,i;
while(spfa(s))
{
for(i=t;~pre[i];i=v[pre[i]^1])
{
w[pre[i]]-=flow[t],w[pre[i]^1]+=flow[t];
ans+=flow[t]*cost[pre[i]];
}
u=v[pre[t]^1];
add(u+1,t,1,0),add(t,u+1,0,0);
int y=(u-n)%sum,x=(u-n-y)/sum+1;
for(i=1;i<=n;++i) add(i,u+1,1,a[i][x]*(y+1)),add(u+1,i,0,-a[i][x]*(y+1));
}
}
int main()
{
int i,j,x;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)
{
scanf("%d",&x),sum+=x;
add(s,i,x,0),add(i,s,0,0);
}
s=0,t=n+m*sum+1;
for(i=1;i<=m;++i) add(id(i,1),t,1,0),add(t,id(i,1),0,0);
for(i=1;i<=n;++i)
{
for(j=1;j<=m;++j)
{
scanf("%d",&a[i][j]);
add(i,id(j,1),1,a[i][j]),add(id(j,1),i,0,-a[i][j]);
}
}
while(spfa(s)) dinic();
printf("%d",ans);
return 0;
}