一句话题意:
基本是多重背包裸题.
分析:
训练赛时的一道题,然而当时忘了多重背包是什么样的了…急中生智,发明了一种算法,感觉效率还是蛮高的.(之后在VJ上发现了一个和我一模一样思路的,也谈不上发明吧)
这是在完全背包上加了一个cnt数组,让完全背包无限放变成了有限个,cnt数组就是记录第i个物品用了几个.
下面先放上一个完全背包的代码:
for (int i = 1; i <= n; i++)
for (int j = data[i].value; j <= cash; j++)
if(dp[j - data[i].value])
dp[j] = true;
如上面代码所示,dp[j]代表使用前i种面额(无限使用)能不能到达j.
但此题每种货币有数量限制,那么就增加一个cnt数组限制第i种面额的使用次数.cnt[j]就表示在全部前i-1种面额组成的所有面额的基础上,再用几张第i中面额,能组成j.
比如在前i种循环中dp[j - v]标记为true,第i中面额为v,数量为k,只要满足:
dp[j - v]为true,(j - v的数量,再加上第i种面额v,就能到达j)
且cnt[j - v] < k(这种货币的使用次数在组成j - v后使用不超过k - 1次)
那么使用第[i]种面额就能组成j.
但是能组成j,此时不要先标记在dp数组中,cnt数组中有标记就可以了,其实不用刻意的去标记,使用了一次第i中货币组成了[j],cnt数组的j位置出就是一个大于1的数.
现在理一理思路:
前i - 1种货币组成的面额标记在dp数组中,
在前i - 1种的基础上新组成的面额标记的cnt数组中(可能会有重复)
每一种货币跑完以后,就把cnt数组中的标记转移到dp数组中就可以了
我们用一次新的面额,都清零一次cnt数组,cnt数组里存的是当前物品的使用次数.
for (int i = 0; i <= n - 1; i++)
{
memset(cnt, 0, sizeof(cnt));//cnt数组标记的是第i种货币的使用次数,每次循环都要清零
for (int j = data[i].value; j <= cash; j++)
{
if(dp[j - data[i].value] && data[i].num)
{
cnt[j] = 1;//在dp中发现j可以达到,那么此时肯定使用的是第一张
}
else if(cnt[j - data[i].value] && cnt[j - data[i].value] < data[i].num)
{
cnt[j] = cnt[j - data[i].value] + 1;//在cnt数组中发现j可以达到,那么肯定使用不止一张
}
}
for (int j = 0; j <= cash; j++)//把cnt数组中的标记转移到dp数组中
if(cnt[j])
dp[j] = true;
}
这样跑完第i遍后,dp数组内存的就是所有能组成的面额的情况了.
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <cstdio>
using namespace std;
bool dp[100010];
int cnt[100010];
struct bill
{
int num, value;
}data[100];
int main()
{
int cash, n;
while(cin >> cash)
{
memset(dp, false, sizeof(dp));
dp[0] = true;
cin >> n;
for (int i = 0; i <= n - 1; i++)
scanf("%d %d", &data[i].num, &data[i].value);
for (int i = 0; i <= n - 1; i++)
{
memset(cnt, 0, sizeof(cnt));//cnt数组标记的是第i种货币的使用次数,每次循环都要清零
for (int j = data[i].value; j <= cash; j++)
{
if(dp[j - data[i].value] && data[i].num)
{
cnt[j] = 1;//在dp中发现j可以达到,那么此时肯定使用的是第一张
}
else if(cnt[j - data[i].value] && cnt[j - data[i].value] < data[i].num)
{
cnt[j] = cnt[j - data[i].value] + 1;//在cnt数组中发现j可以达到,那么肯定使用不止一张
}
}
for (int j = 0; j <= cash; j++)//把cnt数组中的标记转移到dp数组中
if(cnt[j])
dp[j] = true;
}
for (int i = cash; i >= 0; i--)
{
if(dp[i])
{
cout << i << endl;
break;
}
}
}
return 0;
}