题目描述
题目释义
n个砝码每个都可以放到天平的任意一端,求能称出多少种重量。
算法设计
算法选择
大多数人第一反应当然是暴力,毕竟N在100以内……
但是暴力出奇迹毕竟是不妥当的,算法人自然要寻求更高效的解法
此处可以隐约感觉到前i个砝码能称出的重量应该与前i-1个砝码能称出的重量有关,所以可以分析此题是否符合动态规划的条件
dp分析:
定义:f[i][j]表示考虑前i个砝码能否称出重量j,能f[i][j]=1,不能f[i][j]=0
为何如此定义?
首先,我们需要用i来表示前i个砝码。
关键就是为何想到dp表的含义是是否称量的出来呢而不是重量数或者其它的什么东西?
关键就在于前i个砝码能否称出重量j与前i-1个砝码能否称出重量x有关,即只与前i-1个砝码究竟能称出哪些重量有关。
所以这里用j表示重量j能否被称出。所有可能出现的重量都包含在内,当然就用0和1来标识这个重量有没有可能出现。
状态转移
接下来考虑如何求前i个砝码能否称出的重量j
根据动态规划的核心:解决问题时其所需要的子问题已全部解决
在考虑前i个砝码能否称出重量j时,前i-1个砝码对于每一个j是否能够将其称出均已得到。
对于第i个砝码
- 若前i - 1个砝码能称出重量j,则前i - 1个砝码必然也能称出重量j,称的时候不取第i个砝码即可
- 若前i - 1个砝码能称出j - w[i] (w[i]记录第i个砝码的重量),称的时候取第i个砝码即可称出j
- 若前i - 1个砝码能称出j+w[i],称的时候将第i个砝码放在天平另一端即可称出j
那么,如果j - w[i]产生了负值该如何处理?
由于将重量为w[i]砝码放在天平的另外一端可以假象为将重量为-w[i]的砝码放在前i - 1个砝码同一端,所以如果前i个砝码能够称出重量j,那么前i个砝码同样能够称出重量-j(将前i个砝码放到天平另一端即可)。即f[i][j] = f[i][-j],所以我们直接对j - w[i]取绝对值,这样就避免了出现负下标的情况。
例如样例
对于f[2][3],此时我们已经知道前i-1个砝码对于每一个的j能否将其称出。
- f[1][3] = 0,即第一个砝码无法称出3这个重量
- f[1][7] = 0,即无法通过在天平另一端放i来称出重量j
- f[1][1] = 1,即可以通过将砝码2放在1的另一端来称出重量j
综上所述,这三个条件满足任意一个就能够称出重量j,所以他们之间是或的关系。
状态转移方程就可以写为:
f[i][j] = f[i - 1][j] || f[i - 1][j + w[i]] || f[i - 1][abs(j - w[i])]
数据处理
定义全局变量n,w[101],f[101][200001]分别表示数量、重量、dp
将这些变量定义为全局变量是一个很好的习惯。全局变量会被自动赋予初始值,而且能够在任意函数内使用。
int n, w[101], f[101][200001];
定义weight为砝码重量之和,weight即为j的最大值
cin >> n;
int weight = 0;
for (int i = 1; i <= n; ++i) {
cin >> w[i];
weight += w[i];
}
初始化:
f[1][0] = 1;
f[1][w[1]] = 1;
第一个砝码自然只能称出重量0和重量w[1]
算法逻辑
对于第i个砝码,算出其能否称出重量j,但凡其满足上述三种条件之一就能够称出
for (int i = 2; i <= n; ++i) {
for (int j = 0; j <= weight; ++j) {
f[i][j] = f[i - 1][abs(j - w[i])] || f[i - 1][j + w[i]] || f[i - 1][j];
}
}
输出处理
自然是对于前i个砝码,如果能称出就ans++
int ans = 0;
for (int i = 1; i <= weight; ++i) {
if (f[n][i]) {
ans++;
}
}