POJ-1973(并行DP)

题目: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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值