一道用来防AK的题,但是被我们给弄出来了,还是挺可以的。
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.
3 10 1 2 4 2 1 1 2 5 1 4 2 1 0 0Sample Output
8 4
这道题就是要我们求在一定范围内,能够用手头上的一定数量的钱构成几种类型的总和,譬如一个一块和一个两块,在三块钱范围内能组成{1}、{1+2}、{2}的组合。
思路:这道题如果用普通的多重背包,例如这样(就会超时):
for(int i=0; i<n; i++)
{
for(int j=1; j<=coin[i].b; j++)
{
for(int k=m; k>=coin[i].a*j; k--)
{
if(dp[k-coin[i].a] && !dp[k])
{
dp[k]=1;
sum++;
}
}
}
}
既然都说过了会超时,当然也会讲不超时的算法和思路,
上面我们用了3个for循环O(n^3)的算法当然的T,我们发现第三个for中的
int k=m; k>=coin[i].a*j; k--
会被一遍遍的遍历,那么我们换种方式,假如知道此时取了几次岂不是更好,那么用一个s数组来存储到达这一个值我们所用去的步数就可以了。
利用s[i]与dp[i]同步的方法,记录到达这个值时候的步数,我们仅需要在循环中假如条件:
s[k-coin[i].a]<coin[i].b
即可,但为什么取小于符是因为原式应当为s[k]<=coin[i].b,但由于s[k]为接下来的处理量,所以我们才对它的前一步进行处理。
完整代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n,m;
struct node
{
int a,b; //价值,数量
}coin[105];
int dp[100005];
int s[100005];
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)break;
memset(coin, 0, sizeof(coin));
memset(dp, 0, sizeof(dp));
dp[0]=1;
int sum=0;
for(int i=0; i<n; i++)
{
scanf("%d",&coin[i].a);
}
for(int j=0; j<n; j++)
{
scanf("%d",&coin[j].b);
if(coin[j].a*coin[j].b>m)
{
coin[j].b=m/coin[j].a;
}
}
for(int i=0; i<n; i++)
{
memset(s, 0, sizeof(s));
for(int k=coin[i].a; k<=m; k++)
{
if(dp[k-coin[i].a] && !dp[k] && s[k-coin[i].a]<coin[i].b)
{
dp[k]=1;
s[k]=s[k-coin[i].a]+1;
sum++;
}
}
}
printf("%d\n",sum);
}
return 0;
}