【背包问题】大容量背包问题(超大背包)


题目描述

N件物品和一个容量为W的背包。第i件物品的重量是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

输入描述

第一行为N(1≤N≤40),W(1≤V≤1015)。

下面N行,第i行描述第i个物品的w[i] (1≤w[i]≤1015) ,c[i] (1≤v[i]≤1015),用一个空格分隔。

输出描述

输出只有一个数,最大总价值。

Input

3 225274242
70498827 830583485
72910089 759360759
80945586 1095298545

Output

2685242789


分析

大容量背包问题的特点是物品数量很少,但物品的价值特别大,因此不能使用动态规划求解,通用的做法是枚举+二分,以下分步骤来描述。

①枚举前半部分物品:

n最大是40,也就是最多有240 种组合,如果全部枚举,显然会超时,因此先枚举前半部分物品。对于n件物品,每一件物品都有选和不选两种状态,很自然会想到用一个n位二进制数来表示物品的选择情况。

如何获取这个二进制数呢?n件物品对应2n 种状态,也对应了从02n 的十进制数,于是,考虑枚举这个十进制数。i02n 进行遍历,每遍历到一个十进制数,就看这个数中的哪些位是1

例如,对于十进制数5来说,其二进制数是101,第02位是1。观察到把101右移0位后其最低位是1,右移2位后最低位也是1,于是可以总结出,如果右移j位其最低位是1,那么这个二进制数的第j位就是1,换句话说,n件物品中的第j件在这种情况下是选中的,而其他情况下则没有选中。

按照这个规律,就可以枚举出所有的选择情况。枚举完之后,按第一维(即物品的总重量)升序排列数组。

②去掉显然不对的结果:

现在得到的是按重量升序排列的数组,考察数组中的任意两个元素,如果i < j,那么arr[i].first <= arr[j].first,即前者重量更小(注意此时的重量是所有前半部分物品选择的总重量),如果arr[j].second > arr[i].second,意味着在这种选择情况下,总重量大的选择,总价值反而小,显然这是不合适的,把这种方法丢弃掉即可。

③枚举后半部分物品:

按照①的思想,枚举后半部分物品获得每一种选择下的总重量和总价值。对于每一种总重量,退回到之前已枚举到的结果集中进行查找,首先筛选出前半部分的总重量W1 + W2 <= W的组合,然后在这些所有可行的组合中维护一个最大价值。

在维护最大价值的时候,先找到先半部分中可能的那些重量,由于前半部分的结果集是按重量升序排列的,因此找到一个合适的最大重量之后,前面的所有重量都一定是合适的,在这些所有可能的重量中找一个最大价值就可以了。

找这个最大价值的方法有好几种,可以用lower_bound函数,也可以把这些所有的重量插入到大根堆中再找堆顶元素,我这里使用的是先把他们赋值到另一个数组中,然后使用sort函数排序,找最大的那个元素就可以了。

找到可行的重量集中最大的价值后,就可以比较当前得到的结果和之前得到的结果哪个最大了,维护最大的一个价值并最后输出就做完这题啦~

代码:

/*
 * @Description: Large backpack problem
 * @Author: Zhoujin-SDU
 * @email: 1761806916@qq.com
 * @Date: 2021-05-21 23:47:46
 * @LastEditTime: 2021-05-21 23:47:46
 * @FilePath: \week12\t5.cpp
 */
#include <iostream>
#include <cmath>
#include <algorithm>
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int Max = 42;
typedef long long ll;
ll w[Max], v[Max];
ll N, W; 
pair<ll, ll> arr[1 << (Max / 2)];  //重量,价值
pair<ll, ll> arr2[1 << (Max / 2)];  //重量,价值
pair<ll, ll> temp[1 << Max / 2];
ll ans = 0;

bool cmp(pair<ll, ll>& a, pair<ll, ll>& b)
{
	return a.second < b.second;
}

void solve()
{
	ll n = N / 2;
	for (ll i = 0; i < (1ll << n); i++)
	{
		ll now_w = 0ll, now_v = 0ll;
		for (int j = 0; j < n; j++)
		{
			if (i >> j & 1)   //对于二进制数i,它的第j位是1
			{
				now_w += w[j];
				now_v += v[j];
			}
		}
		arr[i].first = now_w;    //二进制数i表示当前选中的状态,对这个状态来计算当前的价值和重量
		arr[i].second = now_v;
	}

	sort(arr, arr + (1ll << n));     //第一维升序


	ll current_min = arr[0].second;
	ll j = 0;
	for (ll i = 1; i < pow(2, n); i++)
	{
		if (arr[i].second > current_min)
			arr2[j++] = arr[i];
		else
			current_min = arr[i].second;
	}
	/*cout << "start" << endl;
	for (int i = 0; i < j; i++)
		cout << arr2[i].first << " " << arr2[i].second << endl;
	cout << "end" << endl;*/


	for (ll i = 0; i < (1ll << (N - n)); i++)
	{
		ll now_w = 0, now_v = 0;
		for (ll k = 0; k < (N - n); k++)
		{
			if ((i >> k) & 1)
			{
				now_w += w[n + k];
				now_v += v[n + k];
			}
		}
		if (now_w <= W)
		{
			ll v = 0;
			for (ll s = j - 1; s >= 0; s--)
			{
				if (arr2[s].first <= W - now_w)   //在符合重量要求的前提下找最大重量
				{
					for (int p = 0; p <= s; p++)
						temp[p] = arr2[p];
					sort(temp, temp + s + 1, cmp);
					v = temp[s].second;
					break;
				}
			}
			//cout << "v = " << v << endl;
			ans = max(ans, v + now_v);
		}
	}
}

int main()
{
	cin >> N >> W;
	for (ll i = 0; i < N; i++)
		cin >> w[i] >> v[i];
	solve();
	cout << ans << endl;


	return 0;
}

/*
3 225274242
70498827 830583485
72910089 759360759
80945586 1095298545
*/
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值