当前位置: 首页 > news >正文

251025B. 海啸

251025B. 海啸

\(n\) 个物品,物品 \(i\)\(v_i\) 的价值和 \(2^{w_i}\) 的体积。

以及 \(q\) 次修改,每次给出 \(x\) 并令 \(a_x \leftarrow a_x +1\)

每次修改后求出当总体积 \(\le V\) 时的最大总价值。

\[n\le 2\times 10^5,q\le 10^4, V\le 10^{18} \]


先考虑没有修改怎么做

\(w_i\) 相同的所有物品都在第 \(w_i\) 层。

我们先把每一层内部排序,然后考虑从小往大贪心。

对于第 \(w\) 层,假如 \(V\) 二进制下第 \(w\) 位为 \(1\),那么取出最大的价值 \(v_0\),将 \(v_0\) 累加到答案里,然后将其删去。

对于剩下的数,我们从大到小将每两个相邻的物品捆绑加入下一层。

这个过程可以归并解决。


考虑怎么带上修改?

我们从 \(w_x\) 开始依次枚举层,并考虑他造成的影响。

考虑到我们仅关心价值,不关心具体是哪个物品贡献的,我们在当前层二分找到第一个 \(\le v_x\) 的,然后将他 \(+1\)

然后我们模仿刚才的过程,将这个物品和相邻的捆绑,并在下一层二分找到对应的物品,将权值 \(+1\)

一直重复这个过程直到某一层 \(w\) 上这个价值是该层最大值,并且 \(V\) 二进制下第 \(w\) 位为 \(1\)。那么直接将答案 \(+1\),然后退出这个过程。


时间复杂度 \(\mathcal O(n\log V+q\log n\log V)\)

code
#include <iostream>
#include <algorithm>
#include <cstring>const int N = 3e5 + 7, W = 60;
typedef long long i64;
#define rep(i,a,b) for(int i(a);i<=(b);++i)namespace suki {i64 sp, ans;
int n, q, w[N], v[N];
std::basic_string<i64> g[W]; 
auto cmp = std::greater<i64>();inline void reset() { ans = 0; for(int i = 0; i < W; ++i) g[i].clear(); }inline void main() {std::cin >> n >> sp >> q;rep(i, 1, n) std::cin >> w[i] >> v[i], g[w[i]] += v[i];for(int i = 0; i < W; ++i) std::sort(g[i].begin(), g[i].end(), cmp);auto work = [&](int k) {if(k > 0) {int origs = g[k].size(), shift = sp >> (k - 1) & 1;for(int i = shift; i + 1 < (int)g[k-1].size(); i += 2)g[k] += g[k-1][i+1] + g[k-1][i];if((g[k-1].size() - shift) & 1) g[k] += g[k-1].back();std::inplace_merge(g[k].begin(), g[k].begin() + origs, g[k].end(), cmp);}if((sp >> k & 1) && !g[k].empty()) { ans += g[k][0]; }};for(int i = 0; i < W; ++i) work(i);std::cout << ans << "\n";for(; q--; ) {int x; std::cin >> x;auto modify = [](int id) {i64 val = v[id]++;for(int k = w[id]; k < W; ++k) {int p = std::lower_bound(g[k].begin(), g[k].end(), val, cmp) - g[k].begin();int shift = sp >> k & 1; g[k][p]++;if(shift && (p == 0)) { ++ans; return ; }int q = ((p - shift) ^ 1) + shift;if(0 <= q && q < g[k].size()) val += g[k][q];}};modify(x);std::cout << ans << "\n";}
}};int main() {std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);int t; std::cin >> t;for(; t--; ) { suki::reset(), suki::main(); }
}

之前见过一道相似的题,但是找不到原题了。题意如下:

给定一个大小为 \(n\) 的可重集 \(S={a_i}\)

维护以下两种操作:

  • 将第 \(l\) 大到第 \(r\) 大之间的所有数 \(+1\)

  • 询问第 \(l\) 大到第 \(r\) 大之间的所有数的和。

\[n\le 10^6 \]


我们初始先将 \(a\) 排序,然后直接按顺序放到线段树上,所以初始时整个序列是有序的。

对于一次修改操作 \([l,r]\),令 \(x=a_l,y=a_r\)\(p_0\)\(y\) 第一次出现的位置,\(p_1\)\(y\) 最后一次出现的位置。

假如我们将整个序列拆成三部分:\([1,p_0-1],[p_0,p_1],[p_1+1,n]\)

对于第一部分,我们修改的是一个后缀,所以直接在原位置上加不会破坏有序的性质。

对于中间的部分,由于值全部相同,我们可以将位于前缀的修改平移到后缀上,这个时候仍然不会破坏有序的性质。

对于最后一部分,我们没有修改,所以不会破坏有序的性质。

第一部分的最后一个数修改后 \(\le y\),中间部分的第一个数修改后 \(\ge y\),所以我们将前两段拼起来整体仍然满足有序的性质。

中间部分的最后一个数修改后 \(\le y+1\),最后一部分的第一个数 \(\ge y+1\),所以我们将后两段拼起来整体仍然满足有序的性质。

换句话说我们可以将每个修改拆成最多两个区间,使得直接在原位置上修改后整个序列仍然有序。

例如一开始的序列是 01112223334,我们希望做操作 \([4,8]\)。(011[12223]334

那么我们实际上做的区间加是:011[1222]33[3]4。加完后的结果是:01123333344


总而言之,这两个问题的维护方法,本质上都是利用了 \(+1\) 操作不会造成“交换”的性质,这使得我们直接在原位上对序列进行操作成为可能。

http://www.hskmm.com/?act=detail&tid=39124

相关文章:

  • 用户上下文透传机制详解
  • 品牌故事不会写?这个AI指令可能帮你解决大问题
  • WebSocket
  • JWT令牌
  • 电梯调度编程结对项目总结
  • GuessGame两个版本的区别
  • 第二次作业--田佳吉
  • 电脑频繁卡顿?4个CMD命令揪出后台隐藏进程
  • 2025_软件工程师课程辅导
  • Graphiti:为智能体构建实时知识图谱,引领更聪明的 AI 时代
  • 《《《es相关
  • 人资新手必看,企业绩效的意义
  • 题解:P14309 【MX-S8-T2】配对
  • HuggingFace 库使用小技巧
  • 启动分布式mapreduce的过程以及prompt
  • 【ArcMap】复制选中的线并将其上移一段距离
  • 题解:AT_apc001_h Generalized Insertion Sort
  • 记一次thinkphp3.2项目迁移失败的原因。 is currently unable to handle this request. HTTP ERROR 500
  • 20232310 2025-2026-1 《网络与系统攻防技术》实验三实验报告
  • [SWPUCTF 2024 秋季新生赛]http标头 WP
  • 20251025 之所思 - 人生如梦
  • Jerrum–Sinclair 全有或全无定理
  • 一种解决所有 OI 问题的算法:Dream 算法
  • CobaltStrike流量分析
  • 【论文阅读】ASPS: Augmented Segment Anything Model for Polyp Segmentation - 指南
  • RuoYi-Cloud 认证实现
  • 初步学习计算机相关知识有感 - fang
  • 2025年自动上料机厂家权威推荐榜:螺旋上料机/真空上料机/粉末上料机,高效输送系统精准选型指南
  • 用代码将txt分别转换成列表和字典
  • 每日反思(2025_10_25)