目录
- HDOJ5616 Jam's balance
- 题目描述
- 背景
- 输入
- 输出
- 题解
- 解法一
- 解法二
- 优化
- 打赏
HDOJ5616 Jam’s balance
题目描述
背景
有 N N N个已知质量的砝码,分别询问给出的 M M M个质量能否被称出
输入
- 第一行输入一个变量 T T T,表示有 T T T组数据;
- 第二行输入一个变量 N N N,表示有 N N N个砝码;
- 第三行输入 N N N个变量 w 1 , w 2 , ⋯ , w n w_1, w_2, \cdots, w_n w1,w2,⋯,wn,分别表示这 N N N个砝码的质量;
- 第四行输入一个变量 M M M,表示要询问的质量有 M M M个;
- 第五行输入 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[j−v[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[j−w[i]]+w[i]),但是该问题有一些不同,因为砝码可以放在两边,所以不是简单的价值相加,但是考虑用 w [ i ] w[i] w[i]加上前 i − 1 i - 1 i−1个砝码组合出的某个方案时还是可以使用这个方程的
现在考虑相减的情况,假设要用第 i i i个砝码更新 f [ j ] f[j] f[j],那么对第 i i i个砝码的使用有两种情况,一是用前 i − 1 i - 1 i−1个砝码组合出的某个方案减去 w [ i ] w[i] w[i],二是用 w [ i ] w[i] w[i]减去前 i − 1 i - 1 i−1个砝码组合出的某个方案
第一种情况和相加类似,易得 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 inlineconst 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 inlineconst 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;
}
打赏
制作不易,若有帮助,欢迎打赏!