題目鏈接
題目描述
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
HINT
【樣例說明】
廚師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不會閒着,總等待時間更短。
可以證明,沒有更優的點餐方案。
【數據範圍】
對於100%的數據,n <= 40, m <= 100, p <= 800, ti,j <= 1000(其中p = ∑pi,即點菜同學的總人數)。
題解
這道題和bzoj1070: [SCOI2007]修車是一樣的,只是數據範圍擴大了很多。不會構圖的可以先寫修車。傳送門:bzoj1070修車。
數據範圍擴大後直接做會T,我們可以動態加邊。注意到其實很多邊都是廢的,比如沒有倒數第一個做的菜就不可能有倒數第二個做的菜。
所有我們在某位廚師在做倒數第k道菜後再把他做倒數第k+1道菜的邊加上。實現方式可以看代碼。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
#define N 100005
struct edge{
int x,next,cost,data,f;
}e[3000000];
int first[N],c[N],v[N],dis[N],ti[50][110],d[N],pre[N],tot=1,n,m,p,q,s,t,sum;
void add(int x,int y,int d,int c){
e[++tot].x=y;
e[tot].data=d;
e[tot].cost=c;
e[tot].f=x;
e[tot].next=first[x];
first[x]=tot;
if(~tot&1) add(y,x,0,-c);
}
bool spfa(){
memset(dis,63,sizeof(dis));
memset(v,0,sizeof(v));
v[c[1]=s]=1; dis[s]=0;
for(p=q=1;p<=q;){
for(int i=first[c[p]];i;i=e[i].next)
if(e[i].data&&dis[e[i].x]>dis[c[p]]+e[i].cost){
dis[e[i].x]=dis[c[p]]+e[i].cost;
pre[e[i].x]=i;
if(!v[e[i].x]){
v[e[i].x]=1;
if(++q==100005) q=1;
c[q]=e[i].x;
}
}
v[c[p]]=false;
if(++p==100005) p=1;
}
return dis[t]!=dis[0];
}
int cost_flow(){
int ans=0,flow,a,b;
while(spfa()){
flow=1000000007;
for(int i=pre[t];i;i=pre[e[i].f]){
flow=min(flow,e[i].data);
if(e[i].f==s){
a=(e[i].x-1)/sum+1;
b=e[i].x%sum+1;
}
}
ans+=flow*dis[t];
for(int i=pre[t];i;i=pre[e[i].f]) e[i].data-=flow,e[i^1].data+=flow;
for(int i=1;i<=n;i++) add((a-1)*sum+b,m*sum+i,1,b*ti[i][a]); //動態加邊
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&d[i]);
sum+=d[i];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) scanf("%d",&ti[i][j]);
s=sum*m+n+1; t=sum*m+n+2;
for(int i=1;i<=sum*m;i++) add(s,i,1,0);
for(int i=1;i<=n;i++) add(i+sum*m,t,d[i],0);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++) add((i-1)*sum+1,m*sum+j,1,ti[j][i]);
printf("%d",cost_flow());
return 0;
}