2022“杭电杯”中国大学生算法设计超级联赛(1)C.Backpack

这篇博客主要探讨了一个使用位运算进行优化的01背包问题。题目要求在给定物品体积和价值的情况下,找出是否存在一种选择方案,使得总体积等于特定值,并且异或和最大。博主首先介绍了基本的动态规划解法,然后逐步优化,通过增加一维状态、二维状态表示以及使用bitset进行空间优化。最后,博主展示了如何通过滚动数组进一步降低空间复杂度,从而实现高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: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();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self_disc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值