题目链接:Problem - 7140
样例输入:
1
5 4
2 4
1 6
2 2
2 12
1 14
样例输出:
14
题意:
有n个物品,每个物品体积为v[i],价值为w[i]
求是否存在一种取法使得总体积为m,若存在求出最大的异或和
题解:
显然01背包。
一:将异或和放入背包中维护,即多开一维来表示
状态表示:bool dp[i][j][k]:前i个物品,异或和为j,体积为k,是否存在
转移方程:
dp[i][j][k] = dp[i][j][k] (k<v[i])
dp[i][j][k] = dp[i][j][k] | dp[i][ j^w[i] ][ k-v[i] ] (k>=v[i])
bool dp[N][N][N];
dp[0][0][0] = 1;//初始化
for (int i = 1; i <= n; i++)
for (int j = 0; j < 1024; j++)
for (int k = 0; k <= m; k++)
{
dp[i][j][k] = dp[i - 1][j][k]; //不取
if (k >= v[i])
dp[i][j][k] |= dp[i - 1][j ^ w[i]][k - v[i]]; //取
}
int ans = -1;
for (int j = 0; j < 1024; j++)
if (dp[n][j][m])
ans = j;
cout << ans << endl;
二、通过01背包的空间优化可用两维表示:
状态表示:bool dp[j][k]:前i个物品,异或和为j,体积为k,是否存在
转移方程:
dp[j][k] = dp[j][k] (k<v[i])
dp[j][k] = dp[j][k] | dp[ j^w[i] ][ k-v[i] ] (k>=v[i])
bool dp[N][N][N];
dp[0][0] = 1;//初始化
for (int i = 1; i <= n; i++)
for (int j = 0; j < 1024; j++)
for (int k = m; k >= 0; k--)
{
dp[j][k] = dp[j][k]; //不取
if (k >= v[i])
dp[j][k] |= dp[j ^ w[i]][k - v[i]]; //取
}
int ans = -1;
for (int j = 0; j < 1024; j++)
if (dp[j][m])
ans = j;
cout << ans << endl;
空间复杂度ok了,但是时间复杂度还是O(n^3),还是不够的,那么怎么优化呢?
三、bitset优化:
考虑到bitset的按位或运算(|)是对每一位进行或运算,将体积用bitset来表示,可通过一次的位运算将所有体积维度更新,故:
状态表示:bitset<N>dp[i][j]:前i个物品异或和为j,dp[i][j]第k位代表异或和为j体积为k是否存在
状态转移:
dp[i][j] = dp[i][j] (k<v[i])
dp[i][j] = dp[i][j] | dp[ i ][ j^w[i] ]<<v[i] (k>=v[i])
tips: 为什么左移v[i]呢?dp[ i ][ j^w[i] ]<<v[i]表示拿了这个物品,需要从体积k-w[i]转移过来,也就相当于dp[i][j]的第k位需要从dp[i][ j^w[i] ]的第k-v[i]位转移过来,所以需要将dp[ i ][ j^w[i] ]左移v[i]位。
bitset<N>dp[N][N];
dp[0][0][0] = 1; //初始化
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < 1024; j++)
{
dp[i][j] = dp[i - 1][j ^ w[i]] << v[i] | dp[i - 1][j]; //转移方程,取/不取
}
}
int ans = -1;
for (int i = 0; i < 1024; i++)
if (dp[n][i][m])
ans = i;
cout << ans << "\n";
可是这样空间复杂度右不行了,考虑
四:滚动数组进行优化:
状态表示:bitset<N>dp[2][j]:前i个物品异或和为j,dp[i][j]第k位代表异或和为j体积为k是否存在
状态转移:
dp[x][j] = dp[x^1][j] (k<v[i])
dp[x][j] = dp[x^1][j] | dp[ x^1 ][ j^w[i] ]<<v[i] (k>=v[i])
最后答案为dp[n&1][j]的第m位为1的异或和(j)的最大值。
详见代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1e9 + 7;
const int N = 1050;
int v[N], w[N];
bitset<N> dp[2][N]; // dp[i][j],i->滚动数组,j->异或和,bitset每一位的当前数位代表体积,对应值0/1代表是否存在
void solve()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) //读入
cin >> v[i] >> w[i];
for (int i = 0; i < 1024; i++)
dp[0][i].reset(), dp[1][i].reset(); //重置为0
dp[0][0][0] = 1; //初始化
for (int i = 1, x = 1; i <= n; i++, x ^= 1) // i->前i个物品,x->滚动数组
{
for (int j = 0; j < 1024; j++) // j->异或和
{
dp[x][j] = dp[x ^ 1][j ^ w[i]] << v[i] | dp[x ^ 1][j]; //转移方程,取/不取
}
}
int ans = -1;
for (int i = 0; i < 1024; i++)
if (dp[n & 1][i][m])
ans = i;
cout << ans << "\n";
}
signed main()
{
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int T;
cin >> T;
for (int i = 1; i <= T; i++)
solve();
}