首页 > 代码库 > 百练 2755 神奇的口袋

百练 2755 神奇的口袋

描述
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。
输入
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。
输出
输出不同的选择物品的方式的数目。
样例输入
3
20
20
20
样例输出
3

枚举,最大2^20.

递归:

#include <iostream>
using namespace std;
int a[30]; int N;
int Ways(int w ,int k ) { // 从前k种物品中选择一些,凑成体积w的做法数目
if( w == 0 ) return 1;
if( k <= 0 ) return 0;
return Ways(w, k -1 ) + Ways(w - a[k], k -1 );
}
int main() {
cin >> N;
for( int i = 1;i <= N; ++ i )
cin >> a[i];
cout << Ways(40,N);
return 0;
}

二维动归:

#include <iostream>
using namespace std;
int a[30]; int N;
int Ways[40][30];//Ways[i][j]表示从前j种物品里凑出体积i的方法数
int main() {
cin >> N;
memset(Ways,0,sizeof(Ways));
for( int i = 1;i <= N; ++ i ) {
cin >> a[i];  Ways[0][i] = 1;
}
Ways[0][0] = 1;
for( int w = 1 ; w <= 40; ++ w ) {
for( int k = 1; k <= N; ++ k ) {
Ways[w][k] = Ways[w][k-1];
if( w-a[k] >= 0) Ways[w][k] += Ways[w-a[k]][k-1];
}
}
cout << Ways[40][N]; return 0;
}


此问题仅在询问容积40是否可达,40是个很小的
数,可以考虑对值域空间-即对容积的可达性进行动态
规划。
定义一维数组 int sum[41];
依次放入物品,计算每次放入物品可达的容积,
并在相应空间设置记录,最后判断sum[40] 是否可达
,到达了几次。
优化:

#include <iostream>
using namespace std;
#define MAX 41
int main(){
int n,i,j,input; int sum[MAX];
for(i=0;i<MAX;i++) sum[i]=0;
cin >> n;
for(i=0;i<n;i++){
cin >> input;
for(j=40;j>=1;j--)
if(sum[j]>0 && j+input <= 40)
sum[j+input] += sum[j];
//如果j有sum[j]种方式可达,则每种方式加上input就可达 j + input
sum[input]++;
}
cout << sum[40] << endl;
return 0;
}
对于sum[j+input] += sum[j];实际指对于所有满足sum[j]>0 && j+input <= 40在原有的sum[j+input]基础上增加sum[j],不断累积n次。另外,sum[input]++的原因是什么呢???

很容易想到sum[input]++就是存储input下标位置的值,那么为什么不能直接也和其他数一样通过sum[input] += sum[0];来得到呢,当我们把j最小设为0不是可以么?也许会有人觉得是这样,但请回头看清题意,物品的体积范围是1-40,也就是说sum[0]恒为0不会发生改变,倘若不特殊处理,那么每次input下标所对应的位置都将少1.


百练 2755 神奇的口袋