这道题弄了一个早上…一看题目是多重背包题直接敲了一个三重for循环,无限TLE,下面附上好几种解法。
A Bank plans to install a machine for cash withdrawal. The machine is able to deliver appropriate @ bills for a requested cash amount. The machine uses exactly N distinct bill denominations, say Dk, k=1,N, and for each denomination Dk the machine has a supply of nk bills. For example,
N=3, n1=10, D1=100, n2=4, D2=50, n3=5, D3=10
means the machine has a supply of 10 bills of @100 each, 4 bills of @50 each, and 5 bills of @10 each.
Call cash the requested amount of cash the machine should deliver and write a program that computes the maximum amount of cash less than or equal to cash that can be effectively delivered according to the available bill supply of the machine.
Notes:
@ is the symbol of the currency delivered by the machine. For instance, @ may stand for dollar, euro, pound etc.
The program input is from standard input. Each data set in the input stands for a particular transaction and has the format:
cash N n1 D1 n2 D2 … nN DN
where 0 <= cash <= 100000 is the amount of cash requested, 0 <=N <= 10 is the number of bill denominations and 0 <= nk <= 1000 is the number of available bills for the Dk denomination, 1 <= Dk <= 1000, k=1,N. White spaces can occur freely between the numbers in the input. The input data are correct.
For each set of data the program prints the result to the standard output on a separate line as shown in the examples below.
735 3 4 125 6 5 3 350
633 4 500 30 6 100 1 5 0 1
735 0
0 3 10 100 10 50 10 10
735
630
0
0
直接暴力三重循环,注定超时,吸取教训...
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int dp[1000005];
int main()
{
int count;
while (scanf("%d",&count)!=EOF)
{
int n;
scanf("%d",&n);
int num[1005];
int coin[11];
memset(num,0,sizeof(num));
memset(coin,0,sizeof(coin));
for (int i = 1;i<=n;i++)
scanf("%d%d",&num[i],&coin[i]);
for (int i = 1;i<=n;i++)
for (int j = 1;j<=num[i];j++)
for (int k = count;k>=coin[i]*j;k--)
dp[k] = max(dp[k],dp[k-j*coin[i]]+j*coin[i]);
printf("%d\n",dp[count]);
memset(dp,0,sizeof(dp));
}
}
这题的硬币种类最多只有10个,其实太小了,直接遍历这些硬币,dp[i]表示能不能组成i块钱,能就dp[i]=1,否则为0,最后向下枚举cash即可
跑了579MS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int dp[1000005];
int main()
{
int count;
while (scanf("%d",&count)!=EOF)
{
int n;
scanf("%d",&n);
int num[20];
int coin[11];
memset(dp,0,sizeof(dp));
memset(num,0,sizeof(num));
memset(coin,0,sizeof(coin));
for (int i = 1;i<=n;i++)
scanf("%d%d",&num[i],&coin[i]);
dp[0] = 1;
for (int i = 1;i<=n;i++)
for (int j = count;j>=0;j--)
{
if (dp[j])
for (int k = 1;k<=num[i];k++)
if (j+k*coin[i] <=count)
dp[j+k*coin[i]]=1;
}
for (int i = count;i>=0;i--)
if (dp[i])
{
printf("%d\n",i);
break;
}
}
}
将多重背包化作完全背包,加个count计数…跑了210MS
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[1000001];
int countn[1000001];
int main()
{
int cash,num,i,j;
int n[15];
int w[15];
while(cin>>cash>>num)
{
for(i=0;i<num;i++)
cin>>n[i]>>w[i];
memset(dp,0,sizeof(dp));
for(i=0;i<num;i++)
{
memset(countn,0,sizeof(countn));
for(j=w[i];j<=cash;j++)
if(dp[j]<dp[j-w[i]]+w[i]&&countn[j-w[i]]<n[i])
{
dp[j]=dp[j-w[i]]+w[i];
countn[j]=countn[j-w[i]]+1;
}
}
cout<<dp[cash]<<endl;
}
return 0;
}
将多重背包用二进制思想分解成01背包
跑了110MS
#include<stdio.h>
#include<cmath>
#include<string.h>
#include<iostream>
using namespace std;
int cash,n,v[10100],dp[1000001];//数组要开的足够大
int main()
{
while(scanf("%d",&cash) != EOF)
{
memset(dp,0,sizeof(dp));
memset(v,0,sizeof(v));
scanf("%d",&n);
int cnt = 0;
for(int i = 1; i <= n ; i ++)
{
int a,b,t = 1;
scanf("%d%d",&b,&a);
if(b != 0)
{
while(t < b)//此处是把b按照二进制分开
{
b = b - t;
v[cnt++] = a * t;
t *= 2;
}
v[cnt++] = b*a;
}
}
if(n == 0 &&cash == 0)
{
printf("0\n");
continue;
}
for (int i = 0;i<cnt;i++)
for (int j = cash;j>=v[i];j--)
dp[j] = max(dp[j],dp[j-v[i]]+v[i]);
printf("%d\n",dp[cash]);
}
}
同样将多重背包用二进制思想化成01背包再用解法一的思想..
跑了63MS….生命不息,优化不止!
#include<stdio.h>
#include<cmath>
#include<string.h>
#include<iostream>
using namespace std;
int cash,n,v[10100],dp[1000001];//数组要开的足够大
int main()
{
while(scanf("%d",&cash) != EOF)
{
memset(dp,0,sizeof(dp));
memset(v,0,sizeof(v));
scanf("%d",&n);
int cnt = 0;
for(int i = 1; i <= n ; i ++)
{
int a,b,t = 1;
scanf("%d%d",&b,&a);
if(b != 0)
{
while(t < b)//此处是把b按照二进制分开
{
b = b - t;
v[cnt++] = a * t;
t *= 2;
}
v[cnt++] = b*a;
}
}
if(n == 0 &&cash == 0)
{
printf("0\n");
continue;
}
dp[0] = 1;
for(int i = 0 ; i < cnt ; i ++)
for(int j = cash ; j >= v[i] ; j --)
dp[j-v[i]] == 1?dp[j] = 1:0;
for(int i = cash; i >= 0;i--)
{
if(dp[i] == 1)
{
printf("%d\n",i);
break;
}
}
}
return 0;
}
通过这道题还弄明白了一点二进制思想,太专业的术语不会表述,打个例子,比如有10个价值为2的物品,用二进制思想的话就是将10用1,2,4,8来组成…,10-1=9,9-2=7,7-4=3,3<8,所以可以用1,2,3,4来组成10,那此时他们的价值就是2,4,6,8,相当于将这10件价值为2的物品转化成了体积分别为1,2,3,4,价值分别为2,4,6,8的四件物品了!哈哈是不是很神奇?