題意:
給出N種貨幣,每種貨幣有a[i]張,
對於給定的錢數X,求出貨幣可以拼出的最接近於並且小於等於X的數值
解法:
把錢的上限X看作體積,並且每種貨幣的面值既是揹包問題裏面的“體積”也是“價值”
相對於0-1揹包問題,該問題屬於多重揹包問題(每組個數有限數量不一),
比較好寫的解法是轉換爲0-1揹包,該方法的時間複雜度是O(V*Σn[i]),對於1276這道題會TLE
分組的解法:
對於每一組物品
我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價於取若干件代換以後的物品。
另外,取超過n[i]件的策略必不能出現。
方法是:將第i種物品分成若干件物品,其中每件物品有一個係數,這件物品的費用和價值均是原來的費用和價值乘以這個係數。
使這些係數分別爲1,2,4,...,2^(k-1),n[i]-2^k+1,且k是滿足n[i]-2^k+1>0的最大整數。
例如,如果n[i]爲13,就將這種物品分成係數分別爲1,2,4,6的四件物品。
設k爲所求的k,num爲給定的該組的個數,下面的程序計算k的值
int k = 0;
int temp = 1;
while (1)
{
if (temp - 1 >= num)
break;
temp *= 2;
k++;
}
k = k - 1;
計算得出k以後對該組重新分配的物品計算對應的係數,設該組物品價值爲val,結果存入fee數組,
第一個係數必須爲1,後面的按照上述方法計算
//分組
int at = 1;
for (i = 0; i < k + 1; i++)
{
if (i != 0)
{
if (i == k)
{
at = num - at * 2 + 1;
}
else
at *= 2;
}
fee[cnt++] = at * val;
}
之後重新運用0-1經典揹包和滾動數組方法求解
#include <iostream>
#include <vector>
#include <map>
#include <list>
#include <set>
#include <deque>
#include <stack>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <cstdio>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <string>
#include <sstream>
#include <cstring>
#include <queue>
using namespace std;
///宏定義
const int INF = 990000000;
const int MAXN = 100000;
const int maxn = 110000 ;
///全局變量 和 函數
int max(int a, int b)
{
return a > b ? a : b;
}
//int T;
int N;
int V;
int f[maxn];
int fee[maxn];
int main()
{
///變量定義
int i, j, m;
while(scanf("%d", &V) != EOF)
{
scanf("%d", &N);
int totnum = 0;
int cnt = 0;
for (int kk = 0;kk < N; kk++)
{
int num, val; //該組的個數和該組的價值
scanf("%d %d", &num, &val);
if (num == 0)
continue;
int k = 0;
int temp = 1;
while (1)
{
if (temp - 1 >= num)
break;
temp *= 2;
k++;
}
k = k - 1;
//分組
int at = 1;
for (i = 0; i < k + 1; i++)
{
if (i != 0)
{
if (i == k)
{
at = num - at * 2 + 1;
}
else
at *= 2;
}
fee[cnt++] = at * val;
}
}
memset(f, 0, sizeof(f));
for (i = 0; i < cnt; i++)
{
for (j = V; j >= 0; j--)
{
if (j >= fee[i])
{
if (f[j] < f[j - fee[i]] + fee[i])
{
f[j] = f[j - fee[i]] + fee[i];
}
}
}
}
int ans = f[V];
printf("%d\n", ans);
}
///結束
return 0;
}
分組的可重用程序
int fee[maxn];
int cnt;
void init(int n)
{
int i, j;
cnt = 0;
for (int kk = 0; kk < n; kk++) //進行分組,存入fee數組
{
int num = m;
int k = 0;
int temp = 1;
while (1)
{
if (temp - 1 >= m)
break;
temp *= 2;
k++;
}
k = k - 1;
//分組
int at = 1;
for (i = 0; i < k + 1; i++)
{
if (i != 0)
{
if (i == k)
{
at = num - at * 2 + 1;
}
else
at *= 2;
}
fee[cnt++] = at * kk;
}
}
}