HDOJ5616 Jam’s balance

题目描述

背景

N N N个已知质量的砝码,分别询问给出的 M M M个质量能否被称出

输入

  1. 第一行输入一个变量 T T T,表示有 T T T组数据;
  2. 第二行输入一个变量 N N N,表示有 N N N个砝码;
  3. 第三行输入 N N N个变量 w 1 , w 2 , ⋯   , w n w_1, w_2, \cdots, w_n w1,w2,,wn,分别表示这 N N N个砝码的质量;
  4. 第四行输入一个变量 M M M,表示要询问的质量有 M M M个;
  5. 第五行输入 M M M个变量 u 1 , u 2 , ⋯   , u m u_1, u_2, \cdots, u_m u1,u2,,um,分别表示要询问的质量

输出

对于每组数据每个询问的质量判断并输出一行 Y E S YES YES N O NO NO

题解

解法一

定义一个数组 f [ ] f[] f[] f [ i ] f[i] f[i]表示当表示出的质量小于等于 i i i时所能表示的最大值,那么本题就可以看成是一个背包问题,而且是每个物品的体积和价值都一致的背包问题
背包问题的状态转移方程是 f [ j ] = m a x ( f [ j ] , f [ j − v [ i ] ] + w [ i ] ) f[j] = max(f[j], f[j - v[i]] + w[i]) f[j]=max(f[j],f[jv[i]]+w[i]),该问题中即为 f [ j ] = m a x ( f [ j ] , f [ j − w [ i ] ] + w [ i ] ) f[j] = max(f[j], f[j - w[i]] + w[i]) f[j]=max(f[j],f[jw[i]]+w[i]),但是该问题有一些不同,因为砝码可以放在两边,所以不是简单的价值相加,但是考虑用 w [ i ] w[i] w[i]加上前 i − 1 i - 1 i1个砝码组合出的某个方案时还是可以使用这个方程的
现在考虑相减的情况,假设要用第 i i i个砝码更新 f [ j ] f[j] f[j],那么对第 i i i个砝码的使用有两种情况,一是用前 i − 1 i - 1 i1个砝码组合出的某个方案减去 w [ i ] w[i] w[i],二是用 w [ i ] w[i] w[i]减去前 i − 1 i - 1 i1个砝码组合出的某个方案
第一种情况和相加类似,易得 f [ j ] = m a x ( f [ j ] , f [ j + w [ i ] ] − w [ i ] ) f[j] = max(f[j], f[j + w[i]] - w[i]) f[j]=max(f[j],f[j+w[i]]w[i]),但是为了防止 f [ k ] ( k > j ) f[k](k > j) f[k](k>j) f [ j ] f[j] f[j]更先更新,从而导致同一砝码使用两次, j j j应该顺序枚举
第二种情况使用 w [ i ] w[i] w[i]更新时,由于 f [ j ] f[j] f[j]表示的是不大于 j j j的最大值,所以应该用 w [ i ] w[i] w[i]减去不小于 w [ i ] − j w[i] - j w[i]j的最小值,那么还需要再定义一个数组 g [ ] g[] g[] g [ i ] g[i] g[i]表示当表示出的质量大于等于 i i i时所能表示的最小值,于是可以得到方程 f [ j ] = m a x ( f [ j ] , w [ i ] − g [ w [ i ] − j ] ) f[j] = max(f[j], w[i] - g[w[i] - j]) f[j]=max(f[j],w[i]g[w[i]j])
定义了一个新的数组,那么它也需要被维护,方程和 f f f是类似的
接着考虑到实际上只需要判断是否可以得到询问的质量,而不需要真正算出最大值和最小值,所以 f , g f , g f,g都可以换为 b o o l bool bool数组并对方程做出改变
最后考虑相加和两种相减计算的先后顺序,相加第一个,之后无论是何种相减,即使减去的方案中使用了同一砝码也会抵消,第二个是相减的情况二,这样在考虑相减的情况一时遇到同一砝码也会抵消,如果后两者反过来,则会导致统一砝码使用多次
代码如下:

#include<cstdio>
#include<cstring>
using namespace std;

const int M = 25;
const int N = 2005;

int main() {
    int t, n, m, mxx, sum;
    int w[M], v[N];
    bool f[N], g[N];

    scanf("%d", &t);

    while(t--) {
        memset(f, 0, sizeof(f));
        memset(g, 0, sizeof(g));
        g[0] = 0;

        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
        scanf("%d", &m);
        for(int i = 1; i <= m; ++i) scanf("%d", &v[i]);

        sum = w[1];
        g[w[1]] = f[w[1]] = g[0] = f[0] = 1;     //初始化不要忘记改了

        for(int i = 2; i <= n; ++i) {
            for(int j = sum += w[i]; j > w[i]; --j) {
                f[j] |= f[j - w[i]];
                g[j] |= g[j - w[i]];
            }
            for(int j = 1; j <= w[i]; ++j) {
                f[j] |= g[w[i] - j];
                g[j] |= f[w[i] - j];
            }
            for(int j = 1, mx = sum - (w[i] << 1); j <= mx; ++j) {
                f[j] |= f[j + w[i]];
                g[j] |= g[j + w[i]];
            }
        }

        for(int i = 1; i <= m; ++i) f[v[i]] ? puts("YES") : puts("NO");
    }
    return 0;
}

解法二

接下来考虑是否可以把两种相减变成一种,核心想法还是背包问题的解法
可以考虑把相加和相减分成两个循环,先单纯考虑相加,再用方案减去 w [ i ] w[i] w[i]的方式对方案进行更新,这样就同时考虑了两种相减
代码如下:

#include<cstdio>
#include<cstring>
using namespace std;

#define il inline

const int M = 25;
const int N = 2005;

int main() {
    bool f[N];
    int t, n, m, sum;
    int w[M];

    scanf("%d", &t);

    while(t--) {
        memset(f, 0, sizeof(f));
        sum = 0, f[0] = 1;

        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);

        for(int i = 1; i <= n; ++i) {
            for(int j = sum + w[i]; j >= w[i]; --j) f[j] |= f[j - w[i]];
            sum += w[i];
        }

        for(int i = 1; i <= n; ++i) {
            int mx = sum - w[i];
            for(int j = 1; j <= mx; ++j) f[j] |= f[j + w[i]];
        }

        scanf("%d", &m);
        while(m--) {
            int x;
            scanf("%d", &x);
            f[x] ? puts("YES") : puts("NO");
        }
    }
    return 0;
}

优化

当同质量的砝码有多个时,如果看成单独的多个砝码,某些本质相同的操作会重复进行,所以不妨看成多重背包再进行二进制优化,二进制优化就是把有多个的某质量砝码依据二进制进行分组等效,如把 8 8 8个质量为 1 1 1的砝码等效质量分别为 1 , 2 , 4 , 1 1 , 2 , 4 , 1 1,2,4,1 4 4 4个砝码,等效前后能表示的质量不变,而砝码数量却下降了
可以发现这个等效本质上就是满 3 3 3 1 1 1,同时两倍质量的砝码数量加 1 1 1,直到不再可以等效
由于可能的砝码数量只有 0 , 1 , 2 0 , 1 , 2 0,1,2,所以不妨使用 b o o l bool bool数组 s s s储存砝码数量, f a l s e , t r u e false , true false,true分别表示 1 , 2 1 , 2 1,2,且 w [ i ] w[i] w[i]表示第 i i i种砝码的质量,再添加一个数组 p o s [ i ] pos[i] pos[i]表示质量为 i i i的砝码在 s s s中的下标,这样每当出现一个新质量的砝码, s s s中元素的总数 t o t tot tot就加 1 1 1
由于数量较小,对于数量为 2 2 2的砝码也可以不采用多重背包的一般方法,直接当成 2 2 2个单独砝码处理即可
代码如下:

#include<cstdio>
#include<cstring>
using namespace std;

#define il inline

const int M = 25;
const int N = 2005;

il int read() {
    int x = 0;
    char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x;
}

int main() {
    bool s[M], f[N];
    int t, n, m, tot, sum, wei;
    int w[M], pos[N];
    memset(pos, 0, sizeof(pos));

    t = read();

    while(t--) {
        memset(f, 0, sizeof(f));
        tot = sum = 0, f[0] = 1;

        n = read();
        for(int i = 1, j; i <= n; ++i) {
            wei = read(), j = pos[wei];
            while(j && s[j]) s[j] = 0, j = pos[wei <<= 1];
            if(!j) pos[wei] = ++tot, s[tot] = 0, w[tot] = wei;
            else s[j] = 1;
        }

        for(int i = 1; i <= tot; ++i) {
            for(int j = sum += w[i]; j >= w[i]; --j) f[j] |= f[j - w[i]];
            if(s[i]) w[++tot] = w[i], s[tot] = 0;
            pos[w[i]] = 0;     //顺便重置pos,这样就不用反复memset
        }

        for(int i = 1; i <= tot; ++i)
            for(int j = 1, mx = sum - w[i]; j <= mx; ++j)
            	f[j] |= f[j + w[i]];

        m = read();
        while(m--) f[read()] ? puts("YES") : puts("NO");
    }
    return 0;
}

打赏

制作不易,若有帮助,欢迎打赏!
赞赏码

支付宝付款码

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部