P2577 [ZJOI2004]午餐

题目链接

重构于\(2020/1/16\)

大致题意

\(n\)个人去排队吃饭,每个人都有一个吃饭时间\(a_i\)和打饭时间\(b_i\),一共有两个打饭窗口,要求安排一种分队和排队方案使得所有人都吃完饭的时间尽量早,输出最佳方案下所有人吃完饭的时刻

\(n,a_i,b_i≤200\)

分析

猜个结论,吃饭吃的越慢的人排在越前面,用邻项交换法易证。这样每个人的吃饭顺序就是固定的了

先来考虑只有一个窗口的情况,不难发现总时间是由最后一个吃完饭的人的吃饭时间他前面的总排队时间组成的,设\(sum_i\)表示前\(i\)个人打饭的时间,那么全部都吃完饭的时间为\(max\{sum_i+b_i\}\)

再回来看两个窗口时的情况,受只有一个窗口的情况的启发,一个比较好想到的状态是设\(f(i,j,k)\)表示在前\(i\)个人中,在\(1\)窗口排队的总时间为\(j\),在\(2\)窗口排队的总时间为\(k\)的最早吃完时间,转移显然:

\(f(i,j,k) = min\begin{cases}max\{f(i-1,j-a_i,k),j+b_i\}&j≥a_i\\max\{f(i-1,j,k-a_i),k+b_i\}&k≥a_i\end{cases}(j+k = sum_i)\)

时间复杂度\(O(n×max\{t\}^2)\),得分\(50pts\)

发现在窗口\(2\)排队的时间为在窗口\(1\)排队的时间的补集,可以直接用总时间减去在窗口\(1\)排队的时间得出,因此\(k\)维度可以去掉

\(f(i,j)\)表示在前\(i\)个人中,在\(1\)窗口排队的总时间为\(j\)时的最早吃完时间,转移:

\(f(i,j) = min\begin{cases}max\{f(i-1,j-a_i),j+b_i\}&j≥a_i\\max\{f(i-1,j),sum_i-j+b_i\}\end{cases}\)

时间复杂度\(O(n×max\{t\})\)

\(code\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 210;
int n;
int f[MAXN][MAXN*MAXN];
struct person{
	int wait,eat;
	bool operator <(person k) const{
			return eat>k.eat;
	}
}d[MAXN];
int sum[MAXN];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		cin>>d[i].wait>>d[i].eat;
	}
	sort(d+1,d+1+n);
	
	for(int i=1;i<=n;i++){
		sum[i] = sum[i-1] + d[i].wait;
	}
	memset(f,0x3f,sizeof(f));
	f[0][0] = 0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=sum[i];j++){
			if(j>=d[i].wait) f[i][j] = min(f[i][j],max(f[i-1][j-d[i].wait],j+d[i].eat));
			f[i][j] = min(f[i][j],max(f[i-1][j],sum[i]-j+d[i].eat));
		}
	}
	int ans = 1145141919;
	for(int i=1;i<=sum[n];i++){
		ans = min(ans , f[n][i]);
	}
	cout<<ans;
}

posted @ 2021-01-16 22:40  xcxc82  阅读(134)  评论(0编辑  收藏  举报