\(\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. 深究可见性,原子性,有序性的解决方案之volatile源码解析

    上节java内存模型(jmm)概念初探大致了解了由于cpu的快速发展,导致的越来越复杂的内存模型诞生,java内存模型相当于是底层内存模型的映射(实际并不是一一映射,但可以借鉴理解),也是衍生出并发三 ...

  2. Qt获取电脑有几个网卡,并获取对应的IPV4

    标题:Qt获取电脑网卡对应的ip | Qt计算电脑有几个网卡 | Qt获取网卡ip信息 | Qt判断获取到的ip是否是IPV4   demo流程: 1.点击搜索网卡按钮,搜索电脑所有的网卡,将网卡名称 ...

  3. 脑洞golang embed 的使用场景

    golang 的 embed 的功能真是一个很神奇的功能,它能把静态资源,直接在编译的时候,打包到最终的二进制程序中. 为什么会设计这么一个功能呢?我想和 golang 的崇尚简单的原则有关系吧.它希 ...

  4. 论文记载: Deep Reinforcement Learning for Traffic LightControl in Vehicular Networks

    强化学习论文记载 论文名: Deep Reinforcement Learning for Traffic LightControl in Vehicular Networks ( 车辆网络交通信号灯 ...

  5. 密码学中的RSA算法与椭圆曲线算法

    PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全.密码学.联邦学习.同态加密等隐私计算领域的技术和内容. 在数字安全领域,加密算法扮演着至关重要的角色.它们确保了信息的机 ...

  6. 力扣1346(java&python)-检查整数及其两倍数是否存在(简单)

    题目: 给你一个整数数组 arr,请你检查是否存在两个整数 N 和 M,满足 N 是 M 的两倍(即,N = 2 * M). 更正式地,检查是否存在两个下标 i 和 j 满足: i != j 0 &l ...

  7. 力扣59(java)-螺旋矩阵Ⅱ(中等)

    题目: 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix . 输入:n = 3 输出:[[1,2,3],[8,9,4],[ ...

  8. DataWorks 如何撑起阿里99%的数据开发?

    阿里妹导读: DataWorks是阿里巴巴自主研发,支撑阿里巴巴经济体99%数据业务建设和治理,每天数万名数据开发和算法开发工程师在使用.从2010年起步到目前的版本,经历了多次技术变革和架构升级,也 ...

  9. [PHP] composer, PHP Fatal error: Allowed memory size of xx bytes exhausted

    终端执行 composer 命令时经常会遇到内存不够的情况. 视情况升级一下 composer,使用 composer self-update. 默认 php 的内存限制是 128M,临时取消 php ...

  10. Roslyn 通过 EmbedAllSources 将源代码嵌入到 PDB 符号文件中方便开发者调试

    本文来告诉大家如何在项目文件里面添加上 EmbedAllSources 属性,将自己的代码嵌入到 PDB 符号文件里面,让开发者们在调试的时候,可以看到库的源代码 是否记得 PDB 符号文件的作用?符 ...