题目描述
有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 种状态,也对应了从0到2n 的十进制数,于是,考虑枚举这个十进制数。i从0到2n 进行遍历,每遍历到一个十进制数,就看这个数中的哪些位是1。
例如,对于十进制数5来说,其二进制数是101,第0、2位是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
*/