题目:http://poj.org/problem?id=1973
一开始根本没想到是DP,看了discuss之后才明白,学来的思路也记录一下吧。
题目中最迷惑的是时间,怎么个并行法,可以这么理解:对于n个人进行工作,设置一定的时限T,则给前n-1个人分配的时间是T,给最后一个人分配的时间也是T,这两个时间上没有加减的关系,所以第n个人可以选择以一部分时间去做A类工作,一部分时间去做B类工作。
时间到底需要多少,0肯定是不行的,那多少才可行,一个时间是让做A类工作最快的人去做这m个A类工作,同时让做B类工作最快的人去做着m个B类工作,其他人啥都不帮忙,两者取最大值(当然这两个人不能是同一个人),这样的情况需要的时间肯定是够的,但我们希望得到一个下限,如果T时间够,那么T+1时间肯定够,很容易想到用二分。
那怎么判断一个时间够不够呢,这是题目最难想的部分,我们到底怎么定义状态(貌似DP题目的关键都是定义状态o(╯□╰)o),这里以f(i, j)表示在规定的时间T内,前i个人在完成j个A类任务的时候,能同时完成多少B类任务,考虑第i个人的情况,第i个人可能被指派完成k个A类任务,0 <= k 且k * a[i] <= T,即这k个任务必须在时限T内完成,剩余的时间则尽量去做B类任务,从而有:
f(i, j) = max{ f(i-1, j-k) + (T-k*a[i])/b[i], 0 <= k <= T/a[i]}
这样,如果f(n, m) >= m,即在规定的T时间内,这n个人在完成m个A类任务的同时如果还能完成m个B类任务,则时间够用。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m, a[101], b[101];
int f[101][101] = {0};
bool enough(int T)
{
memset(f, -1, sizeof(f)); //f[i][j] = -1,表示i个人在时间T内不可能做j个任务
f[0][0] = 0; //0个人自然是能做0个A类任务和0个B类任务的
for(int i = 1; i <= n; ++i){//前i个都不做A类任务
f[i][0] = f[i-1][0] + T / b[i];
}
for(int j = min(m, T/a[1]); j; --j){//第1个人选择做j个A类任务
f[1][j] = (T - j*a[1]) / b[1];
}
for(int i = 2; i <= n; ++i){
for(int j = 1; j <= m; ++j){
for(int k = min(T/a[i], j); k > -1; --k){
//第i个人做k个A类任务,则需要前i-1个人做j-k个A类任务
if(f[i-1][j-k] != -1) f[i][j] = max(f[i][j], f[i-1][j-k] + (T-k*a[i]) / b[i]);
}
}
}
return f[n][m] >= m;
}
int main()
{
int test, ans;
for(scanf("%d", &test); test--; ){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%d%d", a + i, b + i);
int l = 0, r = m * (*max_element(a+1, a+n+1) + *max_element(b+1, b+n+1));
while(l <= r){
int m = (l+r) >> 1;
if(enough(m)){
ans = m;
r = m-1;
}
else l = m+1;
}
printf("%d\n", ans);
}
return 0;
}
另外,可以从状态转移方程中可以看到,本层的状态仅和上一层有关系,因此可以用滚动数组将空间优化到O(m)。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m, a[101], b[101];
int f[101] = {0};
bool enough(int T)
{
memset(f, -1, sizeof(f));
for(int j = min(m, T/a[1]); j > -1; --j){
f[j] = (T - j*a[1]) / b[1];
}
for(int i = 2; i <= n; ++i){
for(int j = m; j >= 1; --j){
int res = -1;
for(int k = min(T/a[i], j); k > -1; --k){
if(f[j-k] != -1) res = max(res, f[j-k] + (T-k*a[i]) / b[i]);
}
f[j] = res;
}
f[0] += T / b[i];
}
return f[m] >= m;
}
int main()
{
int test, ans;
for(scanf("%d", &test); test--; ){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%d%d", a + i, b + i);
int l = 0, r = m * (*max_element(a+1, a+n+1) + *max_element(b+1, b+n+1));
while(l <= r){
int m = (l+r) >> 1;
if(enough(m)){
ans = m;
r = m-1;
}
else l = m+1;
}
printf("%d\n", ans);
}
return 0;
}