\(\bf{用 CDQ 分治可以极大地提升程序运行的速度。}\)

\(\bf{实测在本数据量下,可以在 \color{red}10ms\color{normal}} 内通过所有的测试点!\)

关于折半搜索的内容可以参考这两篇题解:

火星背包 - アイドル

火星背包|折半搜索 - Macw

思路分析

这道题是一道超大背包的典型问题,标准做法应该是使用 折半搜索。搜索两次再将两次的答案分别记录下来,最后拼凑出一个正确的答案。但对于超大背包类型问题,在折半搜索的过程需要记录所有的可以达到的点的状态(选和不选的组合)。一个显而易见的问题就是,这会生成许多 又重又不值钱 的状态,然而我们根本就不需要用这些无效状态。

一个可行的做法就是通过分治做法,基于普通折半搜索的基础上加上分治优化,不断地将区间缩小到原来的二分之一,在每一层合并的时候就可以提前先把那些无效状态删除,防止在后续的合并中被使用。

经过测试,在一般数据下,分治可以在几毫秒之内完成。但在极限数据下(即没有任何的无效数据),程序的运行速度相较于 std 会慢一些。

时间复杂度

本算法的渐进时间复杂度与折半搜索的时间复杂度相同,但是常数比较小。

代码解释

这个算法的关键在于利用 cdq 分治的思想,在每一步中合并左右两边的结果,并通过二分查找找到最优解。这样可以在较短的时间内得到问题的解决方案,尤其适用于处理大规模数据。

CDQ 分治版本

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define int long long
using namespace std; int n, m, path;
// 用于存放每一个物品,w是物品的重量,v是物品的价值。
struct obj{
int w;
int v;
} arr[55];
struct node{
int weight; // 状态所需要的容量
int value; // 状态的价值
int id; // 记录到达该状态的路径。
/*
例如有五个物品:
a b c d e
选择 a c e 三个物品,可以用二进制表示:10101
id 变量用于存储 背包组合(“10101”) 的二进制形式。
*/
};
// res[i] 表示从起始位置为i的区间的所有可能方案。
vector<node> res[55], combo; // 按照weight进行排序,weight相同按照value从小到大排序。
bool cmp(node a, node b){
if (a.weight != b.weight) return a.weight < b.weight;
return a.value < b.value;
} // cdq分治应该是可以过的吧
int cdq(int l, int r){
// 只剩下一个了,直接返回结果,不需要继续递归下去了。
if (l == r){
res[l].clear();
res[l].push_back((node){0, 0, 0});
if (arr[l].w <= m)
res[l].push_back((node){arr[l].w, arr[l].v, 1LL << l});
if (l == 0 && r == n-1) {
path = 1;
return res[l][res[l].size()-1].value;
};
return 0;
}
// 继续cdq分治。
int mid = (l + r) >> 1;
cdq(l, mid); cdq(mid+1, r); // 计算结果,最终将左右两边结果合并起来
if (n == r - l + 1){
int ans = 0; // 记录最终答案。
// 右边的答案。
auto &right = res[mid + 1];
for (int i=0; i<res[l].size(); i++){
int L = 0, R = right.size() - 1;
while(L <= R){
int amid = (L + R) >> 1;
if (res[l][i].weight + right[amid].weight <= m) L = amid + 1;
else R = amid - 1;
}
if (res[l][i].value + right[L - 1].value > ans){
ans = res[l][i].value + right[L - 1].value;
path = res[l][i].id + right[L-1].id;
}
ans = max(ans, res[l][i].value + right[L - 1].value);
}
return ans;
} // 合并左右两半部分区间,看一下在限度内的更佳组合。
// 归并的核心思想,将左右两边结果合并成大的结果。
for (int i=0; i<res[l].size(); i++){
for (int j=0; j<res[mid+1].size(); j++){
if (res[l][i].weight + res[mid+1][j].weight <= m){
int s1 = res[l][i].weight + res[mid+1][j].weight;
int s2 = res[l][i].value + res[mid+1][j].value;
// 合并左右两种方案的路径总和。
// 这里使用了二进制的思想,0表示不选,1表示选。
int s3 = res[l][i].id + res[mid+1][j].id;
combo.push_back((node){s1, s2, s3});
} else break;
}
}
// 排序,依照cmp中定义的规则排序。
sort(combo.begin(), combo.end(), cmp);
res[l].clear();
int mi_v = -1;
for (int i=0; i<combo.size(); i++){
if (combo[i].value > mi_v){
mi_v = combo[i].value;
res[l].push_back(combo[i]);
}
}
combo.clear();
return 0;
} signed main(){
// 加快输入输出,关闭同步流。
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i=0; i<n; i++)
cin >> arr[i].w >> arr[i].v; // 运行分治算法。
cout << cdq(0, n-1) << " ";
// 输出答案。
vector<int> final_result;
for (int i=0; i<n; i++){
if (path >> i & 1){
final_result.push_back(i+1);
}
}
cout << final_result.size() << endl;
for (auto i : final_result) cout << i << " ";
return 0;
}

折半搜索版本

// 不得不说,MInM的代码是真的长。写起来也好麻烦。
#include <iostream>
#include <algorithm>
#include <vector>
#define int long long
using namespace std; int n, m, mid;
int w[55], v[55];
int maxa, maxb; // node 用于记录dfs可以组合出来的所有状态。
struct node{
int weight; // 状态所需要的容量
int value; // 状态的价值
int id; // 记录到达该状态的路径。
/*
例如有五个物品:
a b c d e
选择 a c e 三个物品,可以用二进制表示:10101
id 变量用于存储 背包组合(“10101”) 的二进制形式。
*/
};
// 用于存储两次dfs所有可以到达的节点。
vector<node> ans1, ans2; // 按照weight进行排序,weight相同按照value从小到大排序。
bool cmp(node a, node b){
if (a.weight == b.weight)
return a.value < b.value;
return a.weight < b.weight;
} // 第一次深度优先搜索。
void dfs1(int L, int R, int weight, int value, int id){
if (weight > m) return ;
if (L > R){
// 寻找是否有更优的,没有的话就
ans1.push_back((node){weight, value, id});
return ;
}
// 两种选择,选物品或者不选物品。
dfs1(L+1, R, weight, value, id);
// 这里的位运算可以自己动手画一下。
dfs1(L+1, R, weight + w[L], value + v[L], (1LL << (L-1LL)) + id);
return ;
} // 第二次深度优先搜索。
void dfs2(int L, int R, int weight, int value, int id){
if (weight > m) return ;
if (L > R){
ans2.push_back((node){weight, value, id});
return ;
}
// 两种选择,选物品或者不选物品。
dfs2(L+1, R, weight, value, id);
dfs2(L+1, R, weight + w[L], value + v[L], (1LL << (L-1LL)) + id);
return ;
} signed main(){
// 加快输入输出,关闭同步流。
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i=1; i<=n; i++)
cin >> w[i] >> v[i]; // 折半搜索
mid = n >> 1;
dfs1(1, mid, 0, 0, 0);
dfs2(mid + 1, n, 0, 0, 0); // 按照规则排序。
sort(ans1.begin(), ans1.end(), cmp); // 表示截至目前得到的最大value。
int value = 0; // 将ans1中的无效元素清除(非最优解)
vector<node> tmpath;
tmpath.push_back((node){-1, -1, -1});
for (int i=0; i<ans1.size(); i++){
if (tmpath.back().value < ans1[i].value){
tmpath.push_back(ans1[i]);
}
} // 二分拼接前后两半部分的答案。
int maximum = 0, res = 0;
for (int i=0; i<ans2.size(); i++){
int weight = m - ans2[i].weight;
// 寻找最后的,满足 m - weight
int l = 1, r = tmpath.size() - 1;
int ans = 0;
// 寻找最优解,用二分优化。
while(l <= r){
int mid = (l + r) >> 1;
if (tmpath[mid].weight <= weight){
l = mid + 1;
ans = mid;
} else r = mid - 1;
}
// 更新答案
if (ans != 0 && tmpath[ans].value + ans2[i].value > maximum){
maximum = tmpath[ans].value + ans2[i].value;
// 将前后两半部分的路径相加,就可以获得最终的路径。
// 详情见二进制的加减运算。
res = (ans2[i].id) + (tmpath[ans].id);
}
} // 结果输出
vector<int> final_result;
for (int i=0; i<n; i++){
if (res >> i & 1){
final_result.push_back(i+1);
}
} cout << maximum << " " << final_result.size() << endl;
for (auto i : final_result) cout << i << " ";
return 0;
}

【题解】A19337.火星背包的更多相关文章

  1. E - Knapsack 2 题解(超大01背包)

    题目链接 题目大意 给你一n(n<=100)个物品,物品价值最大为1e3,物品体积最多为1e9,背包最大为1e9 题目思路 如果按照平常的背包来算那么时间复杂度直接O(1e11) 这个你观察就发 ...

  2. HDU 1712 ACboy needs your help(分组背包)

    题意:给你n的课程组,每个课程组有m个课程,每个课程有一个完成时间与价值.问在m天内每组课程组最多选择一个,这样可以得到的最大价值是多少 题解:分组背包,其实就是每个课程组进行01背包,再在课程组内部 ...

  3. HDU 4341 分组背包

    B - Gold miner Time Limit:2000MS      Memory Limit:32768KB     Description Homelesser likes playing ...

  4. cdoj 31 饭卡(card) 01背包

    饭卡(card) Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/problem/show/31 Des ...

  5. cdoj 1136 邱老师玩游戏 树形背包

    邱老师玩游戏 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/problem/show/1136 Desc ...

  6. Codeforces Codeforces Round #319 (Div. 2) B. Modulo Sum 背包dp

    B. Modulo Sum Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/577/problem/ ...

  7. POJ1276 - Cash Machine(多重背包)

    题目大意 给定一个容量为M的背包以及n种物品,每种物品有一个体积和数量,要求你用这些物品尽量的装满背包 题解 就是多重背包~~~~用二进制优化了一下,就是把每种物品的数量cnt拆成由几个数组成,1,2 ...

  8. In Action(最短路+01背包)

    In Action Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total S ...

  9. [Usaco2007 Dec]宝石手镯[01背包][水]

    Description 贝茜在珠宝店闲逛时,买到了一个中意的手镯.很自然地,她想从她收集的 N(1 <= N <= 3,402)块宝石中选出最好的那些镶在手镯上.对于第i块宝石,它的重量为 ...

  10. 【USACO】电子游戏 有条件的背包

    题目描述 翰的奶牛玩游戏成瘾!本来约翰是想把她们拖去电击治疗的,但是他发现奶牛们在玩游戏后生产 了更多的牛奶,也就支持它们了. 但是,奶牛在选择游戏平台上的分歧很大:有些奶牛想买 Xbox 360 来 ...

随机推荐

  1. Python 函数:定义、调用、参数、递归和 Lambda 函数详解

    函数是一段代码块,只有在调用时才会运行.您可以将数据(称为参数)传递给函数. 函数可以返回数据作为结果. 创建函数 在Python中,使用def关键字定义函数: 示例 def my_function( ...

  2. openGauss关于PL/SQL匿名块调用测试

    openGauss 关于 PL/SQL 匿名块调用测试 一.原理介绍 PL/SQL(Procedure Language/Structure Query Language)是标准 SQL 语言添加了过 ...

  3. Terraform 系列-批量创建资源时如何根据某个字段判断是否创建

    系列文章 Terraform 系列文章 Grafana 系列文章 概述 前文 Grafana 系列 - Grafana Terraform Provider 基础 介绍了使用 Grafana Terr ...

  4. 算法小白刷了一周 LeetCode 后的思考

    Hi,我是 itchao 我自己工作有 2 两年多的前端开发经验,但是数据结构与算法一直不好,基本就是一个算法小白的水平. 听说大公司面试都要手写算法题,最近为了以后能去更好的公司,然后其实心里比较着 ...

  5. mongodb基础整理篇————设计[四]

    前言 简单整理一下mongodb的设计. 正文 设计三步曲: 第一步:建立基础文档模型 例子: 1对1建模: 1 对多建模: N对N模型: 第二步: 根据读写况细化 遇到的问题: 解决: 查询连表: ...

  6. OpenKruise v1.0:云原生应用自动化达到新的高峰

    ​简介:OpenKruise 是针对 Kubernetes 的增强能力套件,聚焦于云原生应用的部署.升级.运维.稳定性防护等领域. 云原生应用自动化管理套件.CNCF Sandbox 项目 -- Op ...

  7. [FAQ] Quasar SSR: Hydration completed but contains mismatches.

    使用 Quasar SSR 模式在 build 编译目标代码时,如果模板里有在服务端渲染阶段可能无法识别的变量,一般会出现这类提示. 比如在 layout 模板里使用了 this.$q.this.$r ...

  8. [Trading] 日间交易中的成交量分析 - 使用成交量趋势来提高你的效果

    在交易中,成交量代表在特定时期内股票或期货合约的易手单位数量. 交易员将其作为一个关键指标,因为它让他们知道资产的流动性水平,以及他们在接近当前价格的情况下买入或卖出头寸的容易程度,这可能是一个移动的 ...

  9. UOS 开启 VisualStudio 远程调试 .NET 应用之旅

    本文记录的是在 Windows 系统里面,使用 VisualStudio 2022 远程调试运行在 UOS 里面 dotnet 应用的配置方法 本文写于 2024.03.19 如果你阅读本文的时间距离 ...

  10. Ubuntu WSL 下编译并使用OpenJDK12

    一,安装Ubuntu WSL 1.Windows中设置WSL并安装Ubuntu wsl "控制面板"-->"程序"-->"启用或关闭Win ...