有n件物品,他们各自都有体积vi和价值wi,给你一个体积为m的背包,求是否能从这n件物品中取出若干件,使得它们的体积之和恰好为m,所有物品的异或和最大,最大值是多少
这道题是在01背包的基础上的扩展,其中n,m满足 1≤n,m<210,vi,wi 满足 1≤vi,wi<210 ,我们可以用f[i] [j] [k] 表示在前i个物品中选出若干件,异或和为j,体积为k的方案是否存在,那么,我们可以将状态划分为:
①取第i件物品,f[i] [j] [k] = f[i - 1] [j ⊕ w] [k - v]
为什么是j ⊕ w呢?
因为如果取了第i件物品,那么异或和就变成了j ⊕ w ⊕ w = j,体积变成了k - v + v = k,所以这种情况下 f[i] [j] [k] = f[i - 1] [j ⊕ w] [k - v]
②没有取第i件物品,f[i] [j] [k] = f[i - 1] [j] [k]
所以最终状态转移方程为:f[i] [j] [k] = f[i - 1] [j ⊕ w] [k - v] | f[i - 1] [j] [k]
这样的话就是三维,由于题目中给的所有数值都是小于1024的,我们可以用bitset优化成二维 ,用f[i] [j]表示异或值为i,体积为j的方案是否存在
#include <bits/stdc++.h>
using namespace std;
const int N = 1050;
int n,m;
bitset<N> f[N],g[N];
void solve()
{
for(int i = 0;i < 1024;i ++){
f[i].reset();
g[i].reset();
}
f[0][0] = 1;//初始化,异或和为0,体积为0的方案是存在的
cin >> n >> m;
for(int i = 1;i <= n;i ++){
int v,w;
cin >> v >> w;
for(int j = 0;j < 1024;j ++){
g[j] = f[j];//用g[]来记录上一次的状态
g[j] <<= v;//为什么是左移v位呢?这一步表示当异或和为j时,
//取第j件物品,将g[j]左移v位,最终如果存在左移m位的方案时,
//就说明存在使得所有物品体积和为m的方案,即如果存在f[i][m] == 1时,
//就说明存在异或和为i,取得的所有物品体积和为m的方案
}
for(int j = 0;j < 1024;j ++){
f[j] |= g[j ^ w];//取当前这件物品,就是g[j ^ w],不取这件物品就是f[j]
}
}
int ans = -1;
for(int i = 0;i < 1024;i ++){
if(f[i][m]) ans = i;
}
cout << ans << '\n';
}
int main()
{
int t;
cin >> t;
while (t --){
solve();
}
return 0;
}
大家如果有不懂的地方欢迎在评论区留言~
创作不易,留下你的赞叭~