为什么贪心是正确的
现在假设有两个相邻的人
a
a
a和
b
b
b,他们的打饭时间分别是
b
u
y
[
a
]
buy[a]
buy[a]、
b
u
y
[
b
]
buy[b]
buy[b],吃饭时间分别是
e
a
t
[
a
]
eat[a]
eat[a]、
e
a
t
[
b
]
eat[b]
eat[b]。
很容易发现不管哪个放在前面,所有人都打完饭的时间都是确定的。
如果把
a
a
a放在前面,那么所有人吃完饭的时间是
所有人打完饭的时间 +
m
a
x
(
e
a
t
[
b
]
,
e
a
t
[
a
]
−
b
u
y
[
b
]
)
max(eat[b],eat[a] - buy[b])
max(eat[b],eat[a]−buy[b])
如果把
b
b
b放在前面,则为
所有人打完饭的时间 +
m
a
x
(
e
a
t
[
a
]
,
e
a
t
[
b
]
−
b
u
y
[
a
]
)
max(eat[a],eat[b] - buy[a])
max(eat[a],eat[b]−buy[a])
如果把
a
a
a放在前面更优,那么我们可以得到以下的不等式:
m
a
x
(
e
a
t
[
b
]
,
e
a
t
[
a
]
−
b
u
y
[
b
]
)
<
m
a
x
(
e
a
t
[
a
]
,
e
a
t
[
b
]
−
b
u
y
[
a
]
)
max(eat[b],eat[a] - buy[b]) < max(eat[a],eat[b] - buy[a])
max(eat[b],eat[a]−buy[b])<max(eat[a],eat[b]−buy[a])
然后我还是不会解啊!
难道要分类讨论吗?
**不等式成立的充要条件就是,不等号右边的两个式子里面,至少有一个既大于左边的第一个式子,又大于左边的第二个式子:
**要么
$ eat[a] > eat[b] ~~~ $ && $ ~~~ eat[a] > eat[a] - buy[b]$
要么
$eat[b]-buy[a] > eat[b] ~~~ $ && $ ~~~ eat[b] - buy[a] > eat[a] - buy[b]$
( e a t [ b ] − b u y [ a ] > e a t [ b ] eat[b]-buy[a] > eat[b] eat[b]−buy[a]>eat[b]永远不可能成立,舍去)
所以说只有当上面的一组不等式成立时 a a a才应该放在前面,化简也就是 e a t [ a ] > e a t [ b ] eat[a] > eat[b] eat[a]>eat[b]。
由于两个队伍最后的结构都会遵循eat值递减的规律,所以可以先对所有人按eat值递减排序,再为他分配队伍。
如何DP
其实也可以看做是一个背包,从
n
n
n个人中选择部分人,使得最后的指标--------所有人的时间最短。
最基本的思路就是令
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示考虑到第
i
i
i 个人,A队目前排队时间
j
j
j 分钟,B队目前排队
k
k
k 分钟时,所有人吃完饭所需要的时间。
为什么可以这样做?因为对于一个将要新加入队伍的人来讲,前面的人吃饭的时间不会因为他的加入而变化,他加入哪个队伍后只可能是因为他自己吃饭太慢而拖后腿,所以我们判断一下他会不会拖后腿,如果拖了后腿更新答案就可以了。
但是这样内存会不够,所以我们需要进行空间上的优化。
就如上面所说,当考虑到第
i
i
i个人时,所有人的打饭时间是一定的。
如果我们用一个前缀和
s
u
m
[
i
]
sum[i]
sum[i] 记录前
i
i
i 个人打完饭一共需要多长时间,那么有
j
+
k
=
=
s
u
m
[
i
]
j + k == sum[i]
j+k==sum[i] , 就是说如果我知道A队的人排队需要多久,那么前缀和减去他们等待的时间就是B队需要等待的时间。
这样三维的DP就变成二维的啦!
~~~~
实现细节
什么时候需要更新答案?当新人拖后腿的时候。
我们首先考虑把新人加入A队:
这时候有三种可能性:
- 新人拖后腿了,A队的等待时间变长了,这时候需要把答案更新为
w a i t A = t i m e O f B u y i n g D i s h e s + b u y [ i d ] + e a t [ i d ] waitA = timeOfBuyingDishes + buy[id] + eat[id] waitA=timeOfBuyingDishes+buy[id]+eat[id] - 新人吃的挺快的,A队原来的某个人吃的最慢。这时候不需要进行任何更新.
- 新人吃的挺快的,B队原来的某个人吃的最慢,同样不需要更新.
把新人加入B队的情况类似。
边界条件是什么?
- 首先,这题求最短时间,所以除了 f [ 0 ] [ i ] f[0][i] f[0][i], f f f的其他值应该初始化为正无穷大.
- 枚举A队的人排队时间的时候注意不要越界。如果现在的新人是 i i i , 那么排在前面的所有人的排队时间是 s u m [ i − 1 ] sum[i-1] sum[i−1], 第二维的A队等待时间应该在 [ 0 , s u m [ i − 1 ] ] [0, sum[i-1]] [0,sum[i−1]] 范围内。
AC代码
#include <iostream>
#include <algorithm>
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
using namespace std;
const int INF = 1e9;
int n;
int buy[205];
int eat[205];
int sum[205];
int ord[205];
int f[205][40005];
bool cmp(int x, int y){
return eat[x] > eat[y];
}
int main(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> buy[i] >> eat[i];
ord[i] = i;
}
sort(ord + 1, ord + 1 + n, cmp);
for(int i = 1; i <= n; i++)
sum[i] = sum[i-1] + buy[ord[i]];
for(int i = 1; i <= n; i++)
for(int j = 0; j <= sum[n]; j++)
f[i][j] = INF;
for(int i = 1; i <= n; i++){
int id = ord[i];
for(int j = 0; j <= sum[i-1]; j++){
if (f[i-1][j] == INF) continue;
int other = sum[i-1] - j;
//go to list1
int wait1 = max(j + buy[id] + eat[id], f[i-1][j]);
//go to list2
int wait2 = max(other + buy[id] + eat[id], f[i-1][j]);
f[i][j+buy[id]] = min(f[i][j+buy[id]], wait1);
f[i][j] = min(f[i][j], wait2);
}
}
int ans = INF;
for(int i = 0; i <= sum[n]; i++)
ans = min(ans, f[n][i]);
cout << ans << endl;
}