设有1g,2g,3g,5g,10g,20g的砝码各若干枚(其总重<=1000g),要求:
输入:a1,a2,a3,a4,a5,a6(表示1g的砝码有a1个,2g的砝码有a2个.
……20g的砝码有a6个)
输出:Total=n(n表示这些砝码能称出的不同重量的个数,但不包括
一个砝码也不用的情况。
Sample Input:
1 1 0 0 0 0
Sample Output:
Total=3 (表示可以称出1g,2g,3g三种不同的重量)
解答:我们用Q[i,j]表示用前i个砝码是否能称量重量为j的物品。Q[i,j]=1表示可以称量,Q[i,j]=0表示不能称量。那么状态转移方程为:(我们用数组w[i]表示每个砝码的重量,用a[i]表示每个砝码有多少个,1=<i<=6 )
Q[i,j]=Q[i-1,j] or Q[i-1,j-w[i]] or Q[i-1,j-2*w[i]]....or Q[i-1,j-k*w[i]]
其中k=min{a[i],|j/w[i]|}(向下取整),边界条件为:Q[0,0]=1,最终要求的值为Q[6,j]=1的那些j, 1=<j<=N (N=SUM(w[i]*a[i])),设定砝码总的重量为N,代码如下:
//背包问题的扩展:砝码问题a保存每种砝码的个数,w保存每种砝码的重量,n是砝码的种类数。
void WeightPro(int *a,int *w,int n){
//首先计算总的重量
int N=0;
for(int i=1;i<=n;i++){
N+=a[i]*w[i];
}
//首先定义一个二维bool数组
bool **Q=new bool*[n+1];
for(int i=0;i<=n;i++){
Q[i]=new bool[N+1]();
}
Q[0][0]=true;
for(int i=1;i<=n;i++){
for(int j=0;j<=N;j++){
int q=j/w[i];
int k=q>a[i]?a[i]:q;
for(int p=0;p<=k;p++){
Q[i][j]|=Q[i-1][j-p*w[i]];
}
}
}
int count=0;
cout<<"总重量为:"<<N<<endl;
for(int i=N;i>0;i--){
if(Q[n][i]){
count++;
cout<<"能够称量重量为:"<<i<<"的物品"<<endl;
}
}
cout<<"不同重量个数为:"<<count<<endl;
}
时间代价为O(N*n),空间为O(n*N),这个题目的空间可以优化O(N),因为我们看到状态转移方程中的Q[i,j]仅仅依赖于Q[i-1,..]的状态,但要求j是递减,而且这一点是必须的,因为这样才能保证Q[i,j]当前的状态依赖于i-1的状态,其状态转移方程为:(j from N to 0);
Q[j]=Q[j] or Q[j-w[i]] or Q[j-2*w[i]]....or Q[j-k*w[i]]
变种:这个题目可以进一步变形为对于某个重量为m的物品,该组砝码能够称量m所用的最少的砝码为多少,是哪些砝码?这个问题就和之前的找钱问题是一样的了。只不过这里对砝码的数量有限制,只需要在状态转移方程上稍作改动即可。
令Q[i][j]为有前i个砝码称量出m的最少砝码个数,那么状态转移方程为:
(如果砝码个数是无限个)上面这个转移方程实现起来不是>O(N*n)还可以把状态转移方程优化为:Q[i,j]=min{Q[i-1,j-k*w[i]]+k| 0=<k<=min{j/w[i], a[i]} }
Q[i,j]=min{Q[i-1,j],Q[i,j-w[i]] +1 }(如果砝码个数是无限个)上面这个转移方程实现起来就是O(N*n)的了,但空间是O(N*n)的,空间可以进一步优化为O(N)的,对应的状态转移方程为:
Q[j]=min{Q[j],Q[j-w[i]]+1}但如果要记录用了哪些砝码,必须要开一个O(N*n)的空间来trace.
还可以变种为对于一个给定的重量为m的物品,求出所有能够称量出m的砝码组合数,每一个组合分别是?和原始问题一样,我们用Q[i,j]表示用前i个砝码是否能称量重量为j的物品。Q[i,j]=1表示可以称量,Q[i,j]=0表示不能称量。那么状态转移方程为:
其中k=min{a[i],|j/w[i]|}(向下取整),边界条件为:Q[0,0]=1,如何能列出每种组合呢?Q[i,j]=Q[i-1,j] + Q[i-1,j-w[i]] + Q[i-1,j-2*w[i]]....+ Q[i-1,j-k*w[i]]