砝码称重
问题描述
你有一架天平和 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
样例解释
能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11。
1 = 1;2 = 6 − 4 (天平一边放 6,另一边放 4);
3 = 4 − 1;
4 = 4;
5 = 6 − 1;
6 = 6;
7 = 1 + 6;
9 = 4 + 6 − 1;
10 = 4 + 6;
11 = 1 + 4 + 6。
题解
这里使用闫式dp分析法,将分析过程分为状态表示和状态计算两个过程
状态表示
集合:
只考虑前i
个物品,能得出的j
种方案的集合
属性:
是否为空
这里属性也可设置为数量,设置为数量也更好理解些,至于为什么用bool来进行判断,后面会进行解释
状态计算
我们可以将f(i,j)
集合分为三个子集,第一个为不选
第i
个物品,第二个为左边选
第i
个物品,第三个为右边选
第i
个物品
所得状态方程如下:
不选:
f[i - 1][j]
- 1
左边:
f[i - 1][abs(j - w[i])]
- 1
右边:
f[i - 1][j + w[i]]
- 1
选左边的方程由实际含义可得知 f[i][j - w[i]] = f[i][w[i] - j] = f[i][ abs([j] - w[i]) ] 故可以利用取绝对值避免数组下标为负数的情况。
状态转移方程
从上可以得到我们的状态转移方程为
f[i][j] = f[i - 1][j] || f[i - 1][j + w[i]] || f[i - 1][abs(j - w[i])]
- 1
判断前i
个数不选或选左右边后得到的值,如果j
对第i
个数的权值w[i]
进行选择后能与 上一个数i-1
进行选择后 的值进行匹配,则该j
值为true
如果我们将状态表示的属性设为数量,那么此处的状态转移方程应为:
f[i][j] = f[i - 1][j] + f[i - 1][j + w[i]] + f[i - 1][abs(j - w[i])]
- 1
这样到最后其实我们也是判断 选前i
个值第j
个值是否为0,那么我们不如从一开始就将属性设为bool
值,只要不选,和选左右边这三个状态有一个状态是合法的状态,那么f[i][j]
就是合法的,即为true
。
详细代码(C++)
#include <iostream>
using namespace std;
const int N = 110, M = 2e5 + 10; //假设sum=1e5,加上w[i]是会大于1e5的,故取2e5+10较妥当
int n,sum,w[N];
int f[N][M];
int main()
{
cin >> n;
for (int i = 1;i <= n; i++)
{
cin >> w[i];
sum += w[i];
}
f[0][0] = true;
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= sum; j++)
{
f[i][j] = f[i - 1][j] || f[i - 1][j + w[i]] || f[i - 1][abs(j - w[i])];
}
}
int ans = 0;
for(int i = 1; i <= sum; i++) //0不作为选取方案,故从1开始遍历
{
if(f[n][i]) ans++;
}
cout << ans;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31