砝码称重(DP)【第十二届蓝桥杯省赛第一场C++A/B/研究生组】

题目描述
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN

请你计算一共可以称出多少种不同的正整数重量?

注意砝码可以放在天平两边。

输入格式
输入的第一行包含一个整数 N

第二行包含 N 个整数:W1,W2,W3,⋅⋅⋅,WN

输出格式
输出一个整数代表答案。

数据范围
对于 50% 的评测用例, 1 ≤ N ≤ 15 1≤N≤15 1N15
对于所有评测用例, 1 ≤ N ≤ 100 1≤N≤100 1N100N 个砝码总重不超过 105

输入样例:

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。

思路
d p [ i ] [ j ] dp[i][j] dp[i][j]代表使用前i个砝码的情况下能否凑出重量为j的物品。0代表凑不出,1代表可凑出。
得到 d p dp dp数组后只需要遍历一遍 d p [ n ] [ 1 ] dp[n][1] dp[n][1] d p [ n ] [ s u m ] dp[n][sum] dp[n][sum]有多少种情况能凑出即可。(sum为所有砝码的总重,因为可凑出重量不可能超过总重)

状态转移方程推导
情况1——不使用第i个砝码
  直接可得 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][j] dp[i][j]=dp[i1][j]
情况2——将第i个砝码放在右边
   d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j - w[i]]) dp[i][j]=max(dp[i][j],dp[i1][jw[i]]),注意放在右边是 j − w [ i ] j-w[i] jw[i]。假定天平左端需要称的物品重量为 j j j,那么已知第 i i i个砝码是一定要放在右边的,所以剩余砝码需要凑出的重量就是 j − w [ i ] j-w[i] jw[i]。同理可推得砝码放在左边的方程。
情况3——将第i个砝码放在左边
   d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j + w [ i ] ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j + w[i]]) dp[i][j]=max(dp[i][j],dp[i1][j+w[i]])

其实第i个砝码放在哪里对应的是加还是减没搞清楚也不会错反正就一加一减
解法一 y总解法
  因为该解法在砝码放在左边时第二维下标可能是负数,所以第二维整体加一个偏移量让下标合法。
(y总思路真是太清晰力

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
typedef long long ll;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)
#define endl '\n'
using namespace std;
const int N = 2e5 + 10;
const int M = (N / 2);
int w[N];
int dp[105][N];
int main()
{
    IOS;
    int n;
    cin >> n;
    int sum = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i];
        sum += w[i];
    }
    dp[0][M] = 1;//加了一个M偏移量
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = -sum; j <= sum; j++) //-sum代表砝码全放在左边,sum代表砝码全放在右边
        {
            //只要有一种方案可以凑出来,那么dp[i][j + M]就是1
            dp[i][j + M] = dp[i - 1][j + M];
            if (j - w[i] >= -sum) //特判不存在的情况
                dp[i][j + M] = max(dp[i][j + M], dp[i - 1][j - w[i] + M]);
            if (j + w[i] <= sum) //特判不存在的情况
                dp[i][j + M] = max(dp[i][j + M], dp[i - 1][j + w[i] + M]);
        }
    }
    for (int i = 1; i <= sum; i++)
        if (dp[n][i + M]) //如果这种凑法存在,ans++
            ans++;
    cout << ans << endl;
    return 0;
}

解法二(不需要偏移量)
总体思路差不多,但是二维的循环有了优化。

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
typedef long long ll;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)
#define endl '\n'
using namespace std;
const int N = 2e5 + 10;
const int M = (N / 2);
int w[N];
int dp[105][N];
int main()
{
    IOS;
    int n;
    cin >> n;
    int sum = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i];
        sum += w[i];
    }
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = sum; j >= 0; j--) //不能改成从0到sum,会错
        {
            //这里没加特判是因为即使数组越界了值依旧是0,不会对结果造成影响,所以我就懒得写了
            dp[i][j] = max(dp[i - 1][j + w[i]], dp[i - 1][abs(j - w[i])]);
            dp[i][j] = max(dp[i][j], dp[i - 1][j]);
        }
    }
    int ans = 0;
    for (int i = 1; i <= sum; i++)
    {
        if (dp[n][i])
            ans++;
    }
    cout << ans << endl;
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值