砝码称重问题

砝码问题大体上就是一个问你在背包中选 \(n\) 个物品,体积为 \(j\) 的方案是否存在的问题,求解问题的方式可能稍有不同,但核心都是背包问题求方案是否存在

\(Easy\)

题意:给定 \(n\) 个砝码,每个砝码的重量为 \(w[i]\),问随意选择 \(k\) 个砝码(\(1\)<=\(k\)<=\(n\)),能得到的不同重量的个数

// 可以转化为背包问题求恰好装满方案数
// 只不过,这个方案数我们只考虑0/1
// f[i]就表示装满体积i的方案数
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int M = 1010;

int w[] = {1, 2, 3, 5, 10, 20};
bool f[M];

int main()
{
    f[0] = 1;
    for(int i = 0; i < 6; i ++ )
    {
        int cnt;    cin >> cnt;
        while(cnt -- )
        {
            for(int j = M - 1; j >= w[i]; j -- )
                f[j] |= f[j - w[i]];
        }
    }
    
    
    int res = 0;
    for(int i = 0; i < M; i ++ )
        res += f[i];
    // 不包括重量为0的情况
    cout << "Total=" << res - 1 << endl;
    return 0;
}

\(Medium\)

给定 \(n\) 个砝码,问将 \(x\) 个放在砝码左侧,\(y\) 个放在砝码右侧,可以称出的重量有多少种(\(1\)<=\(x+y\)<=\(n\)

思路一

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 1e5 + 10;

int n, m, w[N];
bool f[2][M];   // 滚动数组优化
int res;

/*
f[i][j]:从前i个物品中选,称出重量为j的方案数(j=0/1)
我们规定:j = abs(left-right)
当前物品不选: f[i][j] |= f[i - 1][j]
当前物品选:
    f[i][j] |= f[i - 1][j - w]:放在左侧
    f[i][j] |= f[i - 1][j + w]:放在右侧
    当放在左侧且j-w<0时,例如j=1,w=2,再不妨设此时左侧为2,右侧为1,j=2-1=1
    [2] [1]
    -------
       |
    -------
    我们希望此时在左侧放一个重量w=2的砝码之后left-right=j=1,显然不可能啊!
    因为显然2+2-1=3,但其实,我们换一种角度想,如果left-right=1
    那么我们交换一下左右侧,变为:
    [1] [2]
    -------
       |
    -------
    此时left-right=-1不久存在了么
    因此说,当j-w<0时,由于数组下标不合法,我们可以将j-w改为abs(j-w),它们是等价的
*/

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )   cin >> w[i], m += w[i];
    
    f[0 & 1][0] = 1;
    for(int i = 1; i <= n; i ++ )
        for(int j = 0; j <= m; j ++ )
        {
            f[i & 1][j] |= f[(i - 1) & 1][j];
            f[i & 1][j] |= f[(i - 1) & 1][abs(j - w[i])];
            if(j + w[i] <= m)   f[i & 1][j] |= f[(i - 1) & 1][j + w[i]];
        }
    for(int i = 1; i <= m; i ++ )   res += f[n & 1][i];
    cout << res << endl;
    return 0;
}

思路二

更直观的思路,为了处理负数的问题,添加一个偏移量即可!

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 2e5 + 10, F = 1e5;

int n, m, w[N];
bool f[2][M];   // 滚动数组优化
int res;

/*
f[i][j]:从前i个物品中选,称出重量为j的方案数(j=0/1)
j=left-right
但是注意这样设置j的话,有 -m <= j <= m
因为体积为循环的时候是从 -m 到 m 而不是 0 到 m
*/

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )   cin >> w[i], m += w[i];
    
    f[0 & 1][0 + F] = 1;
    for(int i = 1; i <= n; i ++ )
        for(int j = -m; j <= m; j ++ ) // j=-m
        {
            f[i & 1][j + F] |= f[(i - 1) & 1][j + F];
            f[i & 1][j + F] |= f[(i - 1) & 1][j - w[i] + F];
            if(j + w[i] <= m)   f[i & 1][j + F] |= f[(i - 1) & 1][j + w[i] + F];
        }
    for(int i = 1; i <= m; i ++ )   res += f[n & 1][i + F];
    cout << res << endl;
    return 0;
}

\(Hard\)

给你 \(n\) 个砝码,问去掉 \(m\) 个之后,最多能称出多少种不同的重量

/* 思路:先dfs枚举可能的方案再dp求解答案
 1. 如何dfs?
    (1) 从n个数当中选出n-m个数保留
    (2) 从n个数当中删除m个数
 通过观察题目范围发现,m<=4,很小
 如果我们再对dfs过程进行剪枝
 那么方案(2)需要枚举的方案比(1)少得多
 因此时间复杂度也就比方案(1)小很多
*/

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 23, M = 2300;

int n, m, w[N];
int sum, res;
bool del[N];    // 某个砝码是否被删除
bool f[M];

void dp()
{
    memset(f, 0, sizeof f);
    f[0] = true;
    for(int i = 0; i < n; i ++ )
    {
        if(del[i])  continue;
        for(int j = sum; j >= w[i]; j -- ) 
            f[j] |= f[j - w[i]];
    }
    int cur_res = 0;
    // 注意不包括0
    for(int i = 1; i <= sum; i ++ )    cur_res += f[i];
    res = max(res, cur_res);
}

// 由于每次回溯时del[u]都会置为false
// 因此del无需每次都初始化
void dfs(int u, int cnt)
{
    // 注意cnt==m的判断应该放在前面
    // 否则当u==n时,会直接return漏解
    // 因此在dfs中,有解的情况放前面,无解的放后面
    
    if(cnt == m)    // 再往下dfs没有意义了,剪枝
    {
        dp();
        return ;
    }
    if(u == n)  return ;
    if(n - u + cnt < m)    return ; // del的个数不够,可行性剪枝
    
    
    dfs(u + 1, cnt);    // 不删除当前的砝码
    del[u] = true;      // 删除当前的砝码
    dfs(u + 1, cnt + 1);
    del[u] = false;
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++ )    cin >> w[i], sum += w[i];
    dfs(0, 0);
    cout << res << endl;
    return 0;
}
posted @ 2024-03-18 10:25  光風霽月  阅读(18)  评论(0编辑  收藏  举报