Problem Address:http://acm.hdu.edu.cn/showproblem.php?pid=2191
【思路】
简单的多重背包。
但是多重背包并不简单。以下简单介绍多重背包的三种解法。
第一种:O(V*Σ(Ki))
这就是最平常的解法。把多重背包当做01背包来做。
把每一件物品的k个数量拆分为k个物品,然后进行01背包。
第二种:O(V*∑([log2Ki]))
这是前一个方法的升级。利用了二进制的思想。
把k拆分为1、2、4……2^(t-1)、Ki-2^t+1的数列,其中t是满足Ki-2^t+1>0的最大整数。
可以想象一下,比如7以内的任意数,是可以通过1、2、4这三个数组合而来。
以上两种方法在《背包九讲》中都有讲解。
第三种:O(VN)
这种方法利用了单调队列。可以先了解单调队列。
详细方法参考国家集训队论文《浅谈几类背包问题》。
达到了和01背包一样的时间复杂度。
【代码】
第一种:O(V*Σ(Ki))
#include <iostream>
using namespace std;
const int maxn = 100;
int dp[maxn+5];
int main()
{
int t;
int n, m, p, h, c;
int i, j, k;
scanf("%d", &t);
while(t--)
{
memset(dp, 0, sizeof(dp));
scanf("%d %d", &n, &m);
for (i=0; i<m; i++)
{
scanf("%d %d %d", &p, &h, &c);
for (j=n; j>=0; j--)
{
for (k=1; k<=c && j+k*p<=n; k++)
{
if (dp[j]+k*h>dp[j+k*p])
dp[j+k*p] = dp[j] + k*h;
}
}
}
printf("%d\n", dp[n]);
}
return 0;
}
第二种:O(V*∑([log2Ki]))
#include <iostream>
using namespace std;
const int maxn = 100;
int dp[maxn+5];
int main()
{
int t;
int n, m, p, h, c;
int i, j, k, w, val;
scanf("%d", &t);
while(t--)
{
memset(dp, 0, sizeof(dp));
scanf("%d %d", &n, &m);
for (i=0; i<m; i++)
{
scanf("%d %d %d", &p, &h, &c);
for (k=1; c-k*2+1>0; k*=2)
{
w = k*p;
val = k*h;
for (j=n-w; j>=0; j--)
{
if (dp[j]+val>dp[j+w])
dp[j+w] = dp[j]+val;
}
}
k = c - k + 1;
w = k*p;
val = k*h;
for (j=n-w; j>=0; j--)
{
if (dp[j]+val>dp[j+w])
dp[j+w] = dp[j]+val;
}
}
printf("%d\n", dp[n]);
}
return 0;
}
第三种:O(VN)
#include <iostream>
using namespace std;
const int maxn = 100;
int dp[maxn+5];
int A[maxn+5];
int B[maxn+5];
int L, R;
inline void insert(int a, int b)//插入一个position为a,值为b的元素到队列中
{
while(R>=L && b>B[R]) R--;
R++;
A[R] = a;
B[R] = b;
}
int main()
{
int t;
int n, m, p, h, c;
int i, j;
int d;
scanf("%d", &t);
while(t--)
{
memset(dp, 0, sizeof(dp));
scanf("%d %d", &n, &m);
for (i=0; i<m; i++)
{
scanf("%d %d %d", &p, &h, &c);
for (d=0; d<p; d++)
{
L = 1;
R = 0;
for (j=0; j<=(n-d)/p; j++)
{
insert(j, dp[j*p+d]-j*h);
if (A[L]<j-c)//如果队列的首元素已经失效
L++;
dp[j*p+d] = B[L] + j*h;
}
}
}
printf("%d\n", dp[n]);
}
return 0;
}