题目大意
THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭。每个人打完饭后立刻开始吃,所有人都吃完饭后立刻集合去六教地下室进行下午的训练。现在给定了每个人的打饭时间和吃饭时间,要求安排一种最佳的分队和排队方案使得所有人都吃完饭的时间尽量早。
题解
我们的思路是从简单到复杂。
如果每个队伍里人的编号都是递增的呢?
这样我们很容易想到动规。$f(i,j)$表示到了第$i$个人打完饭后第一个队伍已经排队排了$j$分钟时最晚吃完饭的时间最早时多少。递归式为$f(i,j)=\min\{\max\{f(i-1,j-A_i),j+B_i\}, \max\{f(i-1,j),(\sum_{k=1}^i A_k)-j+B_i\}\}$。这么做满足最优子结构,因为看递归式,即使$f(i-1,\cdots)$再怎么小,它也不会对答案$f(i,\cdots)$造成坏的影响。
如果只有一个队伍呢?
凭生活经验我们显然可以让吃饭时间长的先打饭,否则你能想出别的招吗?
综上所述,先排序,后DP即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_PERSON = 250, MAX_A = 250, INF = 0x3f3f3f3f;
int F[MAX_PERSON][MAX_PERSON * MAX_A];
struct Person
{
int A, B;
bool operator < (const Person& a) const
{
return B > a.B;
}
}_persons[MAX_PERSON];
int TotPerson;
int DP()
{
memset(F, INF, sizeof(F));
F[0][0] = 0;
int sumA = 0;
for (int i = 1; i <= TotPerson; i++)
{
sumA += _persons[i].A;
for (int j = 0; j <= sumA; j++)
{
int left = j >= _persons[i].A ? max(F[i - 1][j - _persons[i].A], j + _persons[i].B) : INF;
int right = max(F[i - 1][j], sumA - j + _persons[i].B);
F[i][j] = min(left, right);
}
}
int ans = INF;
for (int i = 1; i <= sumA; i++)
ans = min(ans, F[TotPerson][i]);
return ans;
}
int main()
{
scanf("%d", &TotPerson);
for (int i = 1; i <= TotPerson; i++)
scanf("%d%d", &_persons[i].A, &_persons[i].B);
sort(_persons + 1, _persons + TotPerson + 1);
printf("%d\n", DP());
return 0;
}