问题描述
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN。
请你计算一共可以称出多少种不同的正整数重量?
注意砝码可以放在天平两边。
输入格式
输入的第一行包含一个整数 N。
第二行包含 N 个整数:W1,W2,W3,⋅⋅⋅,WN。
输出格式
输出一个整数代表答案。
数据范围
对于 50% 的评测用例,1≤N≤15。
对于所有评测用例,1≤N≤100,N 个砝码总重不超过 100000。
输入样例:
3
1 4 6
输出样例:
10
解题思路:
用dp法,分析状态和状态转移的过程,类似于01背包问题,多了一个分支
状态表示:
题目保证砝码不大于100,总砝码重不超过100000,则状态最多为100*100000个,设dp[102][100005]足以表示所有的状态。w[102]表示每个砝码的重量。
状态的意义:
dp[i][j]!=0表示前i个砝码可以称出j重量,反之dp[i][j]=0表示前i个砝码不可以称出j重量,所以首先要将状态全部设置为0.
边界条件:
前0个砝码可以称出0重量,即dp[0][0]=1
状态转移:
dp[i][j]的状态可以由三个方向转移而来,即:
1)让第i个放在天平的重的那边(假定为右边)
2)让第i个不放上天平
3)让第i个放在天平的轻的那边(假定为左边)
由1可得到dp[i][j]可由dp[i-1][j-w[i]]或者dp[i-1][w[i]-j]得来,这里为了避免数组索引为负值用abs()处理,即dp[i][j]可由dp[i-1][abs(j-w[i])]得来
由2可得到dp[i][j]可由dp[i-1][j]得来
由3可得到dp[i][j]可由dp[i-1][j+w[i]]得来
由以上分析可得到,只要dp[i - 1][j] , dp[i - 1][j + w[i]] , dp[i - 1][abs(j - w[i])]任意一项可以实现,则dp[i][j]也可以得到
状态转移方程为:
dp[i][j]=dp[i - 1][j] + f[i - 1][j + w[i]] + f[i - 1][abs(j - w[i])]或者
dp[i][j]=max(dp[i - 1][j] , f[i - 1][j + w[i]] , f[i - 1][abs(j - w[i])])
dp[i][j]只要非0,就表示前i个砝码能称出j重量
本题目的就是求n个砝码能表示出的重量 ,遍历dp[n][j] ,数一数有多少个非0值即可,
代码如下(C++):
#include<iostream>
using namespace std;
typedef long long ll;
int dp[102][100005];//前i个砝码可以称出质量j
int w[102];
int n;
int sum = 0;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> w[i];
sum += w[i];
}
dp[0][0] = 1;//前0件 可以称出重量为0
for (int i = 1; i <= n; i++) { //有n*sum个状态
for (int j = 0; j <= sum; j++) {//这里j不能从1开始 必须从0 第一件物品不可能称出0 但是前i件可能称出0
dp[i][j] = dp[i - 1][abs(j - w[i])] + dp[i - 1][j + w[i]] +dp[i - 1][j];
}
}
int ans = 0;
for (int i = 1; i <= sum; i++) {
if (dp[n][i] != 0) {
ans++;
}
}
cout << ans;
return 0;
}