【题解】P2050 [NOI2012] 美食节

[NOI2012] 美食节

题目描述

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

输入格式

输入文件的第 1 行包含两个正整数 nm,表示菜品的种数和厨师的数量。

2 行包含 n 个正整数,其中第 i 个数为 pi,表示点第 i 种菜品的人数。

接下来有 n 行,每行包含 m 个非负整数,这 n 行中的第 i 行的第 j 个数为 ti,j,表示第 j 个厨师制作第 i 种菜品所需的时间。

输入文件中每行相邻的两个数之间均由一个空格隔开,行末均没有多余空格。

输出格式

输出仅一行包含一个整数,为总等待时间的最小值。

样例 #1

样例输入 #1

3 2 
3 1 1 
5 7 
3 6 
8 9

样例输出 #1

47

提示

厨师 1 先制作 1 份菜品 2,再制作 2 份菜品 1。点这 3 道菜的 3 个同学的等待时间分别为 33+5=83+5+5=13

厨师 2 先制作 1 份菜品 1,再制作 1 份菜品 3。点这 2 道菜的 2 个同学的等待时间分别为 77+9=16

总等待时间为 3+8+13+7+16=47

虽然菜品 1 和菜品 3 由厨师 1 制作更快,如果这些菜品都由厨师 1 制作,总等待时间反而更长。如果按上述的做法,将 1 份菜品 11 份菜品 3 调整到厨师 2 制作,这样厨师 2 不会闲着,总等待时间更短。

可以证明,没有更优的点餐方案。

每组数据的 n,mp 值如下:

测试点编号 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% 的数据,n40m100p800ti,j1000(其中 p=pi)。

题解

此题显然可以想到一种思路是对于每个厨师做过菜之后我们可以更新所有连向它的边(使边权加上当前已做菜的时间),但是显然这种思路是错的,原因在于不能保证当前最优是全局最优。考虑网络流的实质是反悔贪心,上述做法错误即在于无法反悔,于是考虑不更新边权而是新建一个点把所有边新建出来,于此可满足反悔贪心。

同时有另一个思路,我们从前往后考虑每一道菜贡献了多少次,假设厨师a做了k道菜,那么他做的第一道菜会贡献 time×k 的时间,于是可以对k建分层图跑。注意到图上实际有大量节点无用,于是可以跑完某厨师的某层再建当前厨师的下一层,时间复杂度优秀。

#include<bits/stdc++.h>
using namespace std;
inline int rd(){
	int f=1,j=0;
	char w=getchar();
	while(!isdigit(w)){
		if(w=='-')f=-1;
		w=getchar();
	}
	while(isdigit(w)){
		j=j*10+w-'0';
		w=getchar();
	}
	return f*j;
}

const int N=100001,M=5000000;
int head[N],to[M],fro[M],flo[M],val[M],tail=1;
int n,m,cost[51][101],tim[101],all,ans;
int s,t,pla[51],cook[101],cnt;
int fl[N],va[N],fr[N],fa[N];
int par[N];
bool inque[N];

inline void addlin(int x,int y,int a,int b){
	to[++tail]=y;
	fro[tail]=head[x];
	head[x]=tail;
	flo[tail]=a,val[tail]=b;
	to[++tail]=x;
	fro[tail]=head[y];
	head[y]=tail;
	flo[tail]=0,val[tail]=-b;
}
bool spfa(int last){
	for(int i=1;i<=cnt;i++){
		fl[i]=0;
		va[i]=INT_MAX/3;
	}
	fl[s]=last,va[s]=0;
	deque<int>p;
	p.push_back(s);
	while(!p.empty()){
		int u=p.front();p.pop_front();
		inque[u]=false;
		for(int k=head[u];k;k=fro[k]){
			int x=to[k];
			if(!flo[k]||va[x]<=va[u]+val[k])continue;
			va[x]=va[u]+val[k];
			fl[x]=min(fl[u],flo[k]);
			fr[x]=k,fa[x]=u;
			if(!inque[x])p.push_back(x),inque[x]=true;
		}
	}
	return fl[t]!=0;
}

signed main(){
	n=rd(),m=rd();
	s=++cnt,t=++cnt;
	for(int i=1;i<=n;i++){
		int x=rd();
		pla[i]=++cnt;
		addlin(s,pla[i],x,0);
		all+=x; 
	}
	for(int i=1;i<=m;i++){
		cook[i]=++cnt,tim[i]=1;
		par[cnt]=i;
		addlin(cook[i],t,1,0);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)cost[i][j]=rd();
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			addlin(pla[i],cook[j],1,cost[i][j]);
		}
	}
	while(spfa(all)){
		all-=fl[t];
		ans+=va[t]*fl[t];
		int u=par[fa[t]];
//		cout<<u<<":"<<fl[t]<<" "<<va[t]<<"\n";
		tim[u]++;
		cook[u]=++cnt;
		par[cook[u]]=u;
		addlin(cook[u],t,1,0);
		for(int i=1;i<=n;i++){
			addlin(pla[i],cook[u],1,cost[i][u]*tim[u]);
		}
		for(u=t;u!=s;u=fa[u]){
			flo[fr[u]]-=fl[t],flo[fr[u]^1]+=fl[t];
		}
	}
	printf("%d",ans);
	return 0;
}
posted @   flywatre  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示