多重背包,有一点变形。
意思就是给你n
种物品,每种物品都有一个数量和价值,让你把这些物品按照价值分为两份,这两份的价值差值尽可能小,问你这两份的价值分别是多少。
虽然这题只给了一个维度,但是照样能转化为背包来做,很简单,将重量和价值看成一个东西,都引用这一个数组就完事了。
这样,dp[j]
表示的就是“在拿取物品价值总和不超过j
的情况下,最多能拿多少价值的物品”,这样看似像一句废话,但请注意,j
不一定能被正好达到。
题目问的是将总价值尽可能平均分为两份,那其实dp[V/2]
表示的就是可能较少的那份的价值(V
是奇数也没关系,物品价值都是整数,不可能补得上那0.5
)。
所以,只需要将背包的第二层循环运行一半就可以了(上限为V/2
)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std; // 分半,可以转化为多重背包问题,dp[V/2]表示不超过V/2的最大值
int N;
int p[50];
int num[50];
int dp[125001]; // 50*50*100/2
int V;
vector<int> t;
void init()
{
memset(dp, 0, sizeof dp);
V = 0;
}
int main()
{
for (; ~scanf("%d", &N);)
{
if (N < 0) break;
init();
for (int i = 0; i < N; i++)
{
scanf("%d%d", &p[i], &num[i]);
V += p[i] * num[i];
}
for (int i = 0; i < N; i++) // 我们知道dp[V/2]的值就可以得出答案了,所以只求解一半
{
if (p[i] * num[i] >= V / 2)
for (int j = p[i]; j <= V / 2; j++) // 完全背包正序
dp[j] = max(dp[j], dp[j - p[i]] + p[i]);
else
{
t.clear();
for (int k = 1; k < num[i]; k <<= 1)
{
t.push_back(k);
num[i] -= k;
}
t.push_back(num[i]);
for (int k = 0; k < t.size(); k++)
for (int j = V / 2; j >= p[i] * t[k]; j--) // 01背包逆序
dp[j] = max(dp[j], dp[j - p[i] * t[k]] + p[i] * t[k]);
}
}
printf("%d %d\n", V - dp[V / 2], dp[V / 2]);
}
return 0;
}