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)。他想知道的是最小的总等待时间是多少。
Input
输入文件的第1行包含两个正整数n和m,表示菜品的种数和厨师的数量。 第2行包含n个正整数,其中第i个数为pi,表示点第i种菜品的人数。 接下来有n行,每行包含m个非负整数,这n行中的第i行的第j个数为ti,j,表示第j个厨师制作第i种菜品所需的时间。 输入文件中每行相邻的两个数之间均由一个空格隔开,行末均没有多余空格。
Output
输出仅一行包含一个整数,为总等待时间的最小值。
Sample Input
3 2
3 1 1
5 7
3 6
8 9
Sample Output
47
【样例说明】
厨师1先制作1份菜品2,再制作2份菜品1。点这3道菜的3个同学的等待时间分别为3,3+5=8,3+5+5=13。
厨师2先制作1份菜品1,再制作1份菜品3。点这2道菜的2个同学的等待时间分别为7,7+9=16。
总等待时间为3+8+13+7+16=47。
虽然菜品1和菜品3由厨师1制作更快,如果这些菜品都由厨师1制作,总等待时间反而更长。如果按上述的做法,将1份菜品1和1份菜品3调整到厨师2制作,这样厨师2不会闲着,总等待时间更短。
可以证明,没有更优的点餐方案。
每组数据的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 |
对于100%的数据,n <= 40, m <= 100, p <= 800, ti,j <= 1000 (其中p = ∑pi)
Solution
注: 此题详细题解请参考笔者网络流建模基础这一文章。
为什么这篇题解出现的比那个里面的晚。。虽然笔者早就明白这题咋做了,但是总是不愿意把自己prime-dual的费用流转换成EK的,就导致一直连样例都过不去,最后过了样例,T了4个点。。原因就是这个题每次增广时最多找出一条增广路,也就是说多路增广完全是在浪费时间。。换成EK就轻松A掉了。
Code
// luogu-judger-enable-o2
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define inf 1000000001
#define re register
#define ll long long
#define min(a,b) a<b?a:b
#define MAXN 80045
#define MAXM 100001
using namespace std;
const long double eps=0.00000007;
int n,m,s,t;
int head[MAXN],num=-1,tot,dis[MAXN],b[MAXN];
int to[800001],nxt[800001],w[800001],edis[800001],pre[80051];
int a[201][201],p[45],sum,xb[300002],fa[200001],flow[200001];
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int too,int ww,ll dis)
{
nxt[++num]=head[from];
to[num]=too;
w[num]=ww;
edis[num]=dis;
head[from]=num;
}
inline void add(int from,int too,int ww,ll dis)
{
add_edge(from,too,ww,dis);
add_edge(too,from,0,-dis);
}
inline bool bfs()
{
memset(dis,100,sizeof(dis));
memset(b,0,sizeof(b));
queue<int> q;
while(!q.empty())
q.pop();
for(re int i=1;i<=n;i++)
{
fa[i]=-1;
}
b[s]=1;dis[s]=0;fa[s]=0;
flow[s]=inf;q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
b[u]=0;
for(re int i=head[u];i!=-1;i=nxt[i])
{
int v=to[i];
if(w[i]>0&&dis[v]>dis[u]+edis[i])
{
dis[v]=dis[u]+edis[i];
fa[v]=u;
xb[v]=i;
flow[v]=min(flow[u],w[i]);
if(!b[v]){b[v]=1,q.push(v);}
}
}
}
return dis[t]<inf;
}
inline void max_flow()
{
int ans=0;
while(bfs())
{
int k=t;
while(k!=s)
{
w[xb[k]]-=flow[t];
w[xb[k]^1]+=flow[t];
k=fa[k];
}
ans+=flow[t];
tot+=flow[t]*dis[t];
int x=fa[t];
add(x+1,t,1,0);
for(re int i=1;i<=n;i++){
add(i+sum*m,x+1,1,a[i][x/sum+1]*(x%sum+1));
}
}
}
int main()
{
//freopen("date.in","r",stdin);
memset(head,-1,sizeof(head));
n=read();m=read();
for(re int i=1;i<=n;i++){
p[i]=read();
sum+=p[i];
}
s=0;t=n+sum*m+1;
for(re int i=1;i<=n;i++){
add(s,i+sum*m,p[i],0);
for(re int j=1;j<=m;j++){
a[i][j]=read();
add(i+sum*m,(j-1)*sum+1,1,a[i][j]);
}
}
for(re int i=1;i<=m;i++){
add((i-1)*sum+1,t,1,0);
}
max_flow();
cout<<tot;
return 0;
}