(容量超大)or(容量及价值)超大背包问题 ( 折半枚举 || 改变 dp 意义 )

题意 :

以下两个问题的物品都只能取有且只有一次

① 给你 N 个物品,所有物品的价值总和不会超过 5000, 单个物品的价格就可达 10^10 ,背包容量为 B 

② 给你 N (N ≤ 40 ) 个物品,物品的单个价值和重量都达到 10^15 问你在背包容量为 W 

给出 ① 和 ② 问题条件下背包所能装出来的最大价值

 

分析 :

① 因为单个物品的价值实在太大,如果仍然按照普通 0/1 背包的 dp 定义方法数组是开不下的

但是发现价值的总和并不大,所以从价值这里下手,定义 dp[i][j] = 从 1 ~ i 个物品背包价值为 j 时的最小重量

dp 的状态转移方程效仿普通的 0/1 背包即可,但是是取最小 dp[i][j] = min(dp[i][j], dp[i-1][j-v[i]] + w[i])

同样可以使用 0/1 背包的空间优化将 dp 数组转化为一维的来减少空间复杂度

以下是  FZU 2214 的 AC 代码

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn = 5e3 + 10;
const LL INF = 0x3f3f3f3f;
LL dp[maxn], w[555], B;
int v[555];
int N, C;

int main(void)
{
    int nCase;
    scanf("%d", &nCase);
    while(nCase--){
        scanf("%d %lld", &N, &B);
        C = 0;
        for(int i=1; i<=N; i++)
            scanf("%lld %lld", &w[i], &v[i]),
            C += v[i]; /// C 累加的是所有物品的价值总和

        for(int j=0; j<=C; j++)
            dp[j] = INF;/// 初始化 dp 数组应当赋予一个较大的数
        dp[0] = 0;

        for(int i=1; i<=N; i++)
            for(int j=C; j>=v[i]; j--)
                dp[j] = min(dp[j], dp[j-v[i]]+w[i]);

        for(int j=C; j>=0; j--){
            if(dp[j] <= B){///从大到小遍历价值,第一个满足条件的 dp 数组值的下标便是答案
                printf("%d\n", j);
                break;
            }
        }
    }
    return 0;
}
View Code

 

② 因为价值和重量都太过于大,数组是完全开不下了,不能记录状态,所以考虑其他方法。

如果是去暴力,那么就是暴力枚举物品的组合,然后找到一个组合使得价值最大,复杂度为 O( 2^n )

这里 max( n ) == 40 ,所以时间复杂度还是大到无法接受,考虑是否能将问题规模降下来。

考虑折半枚举法,假设现 n == 40,折半思想是先把前 2^20 个物品的组合先枚举预处理出来 2^20 个 w、v

然后如果我们能对于这枚举出来的前 2^20 个和后面 2^20 个物品的某个组合结合然后找出最优的结果

最后从这 2^20 个最优结果中再取最优就是答案,问题就是如何对于预处理出来的前 2^20 个组合与后面结合产生最优

假设现在背包容量为 C ,在前 2^20 个物品组合中取出一个,价值为 vi 重量为 wi 

那么如果我们能从后 2^20 个中找出一个组合使得其在满足重量 w' ≤ C - wi 的情况下价值最大

只要对于后 2^20 个的所有组合处理出其重量和价值,然后根据重量排序且根据价值去重

这样就能用二分查找来加快查找速度!

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 42;
const LL INF = 0x3f3f3f3f;
pair<LL, LL> Pi[1<<(maxn/2)];
LL W[maxn], V[maxn], C;
int N;

int main(void)
{
    //freopen("in.txt", "r", stdin);
    while(~scanf("%d %lld", &N, &C)){

        for(int i=0; i<N; i++)
            scanf("%lld %lld", &W[i], &V[i]);

        int n = N>>1;
        for(int i=0; i<(1<<n); i++){/// 利用二进制法枚举子集,集合个数应当为 2^n
            LL SumW, SumV;
            SumW = SumV = 0;
            for(int j=0; j<n; j++){
                if(i >> j & 1){
                    SumW += W[j];
                    SumV += V[j];
                }
            }
            Pi[i] = make_pair(SumW, SumV);/// 将每个组合的 重量&&价值 用 pair 存起来
        }

        sort(Pi, Pi+(1<<n));/// 按照第一键值(重量)排序
        int num = 1;
        for(int i=1; i<(1<<n); i++)
            if(Pi[num-1].second < Pi[i].second)/// 这样的去重能找出在价值一样的情况下,保存最小的 w
                Pi[num++] = Pi[i];

        for(int i=0; i<num; i++){
            printf("%d %lld %lld", i, Pi[i].first, Pi[i].second);
            puts("");
        }

        LL res = 0;
        for(int i=0; i<(1<<(N-n)); i++){/// 枚举后半段的组合
            LL SumW, SumV;
            SumW = SumV = 0;
            for(int j=0; j<(N-n); j++){
                if(i >> j & 1){
                    SumW += W[n+j];
                    SumV += V[n+j];
                }
            }
            if(SumW <= C){
                int idx = (lower_bound(Pi, Pi+num, make_pair(C-SumW, INF)) - Pi)-1;
                pair<LL, LL> Temp = Pi[idx];
                res = max(res, SumV + Temp.second);
            }
        }

        printf("%lld\n", res);
    }
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/LiHior/p/8320078.html

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中的顺序查找和折半查找是常见的查找算法,但在实际使用中,也会遇到一些问题。下面我就为您列举一些常见的问题及解决方法。 1. 顺序查找和折半查找的时间复杂度分别是多少? 顺序查找的时间复杂度为 O(n),其中 n 为查找表的长度。折半查找的时间复杂度为 O(log n),其中 n 为有序查找表的长度。因此,当 n 较大时,折半查找的效率比顺序查找高。 2. 如何实现对无序查找表的折半查找? 对于无序查找表,我们可以先对其进行排序,然后再使用折半查找。常见的排序算法有冒泡排序、插入排序、选择排序、快速排序、归并排序等。在排序完成后,再使用折半查找即可。 3. 折半查找的查找范围是什么? 折半查找只适用于有序查找表。在每次查找时,折半查找会将查找范围缩小一半,直到找到目标元素或者查找范围为空。因此,使用折半查找时需要保证查找表是有序的。 4. 如何处理重复元素? 在顺序查找中,重复元素的处理比较简单,只需要找到第一个符合条件的元素即可。而在折半查找中,由于可能存在多个相同的元素,因此需要进行特殊处理。一种常见的处理方法是,找到目标元素后,向左右两侧扩展,直到找到所有的目标元素。 5. 如何处理查找失败的情况? 在顺序查找中,查找失败的情况比较容易处理,只需要返回一个特定的标志值即可。而在折半查找中,如果查找失败,也需要返回一个特定的标志值。一般来说,可以返回 -1 或者查找表的长度。 希望这些解答能够帮助您更好地理解和应用顺序查找和折半查找算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值