1 条题解
-
0
「BalticOI 2025」指数 题解
题目大意
给定一个序列 ,每个 表示 。
定义合并规则:
对于区间 ,可以通过不同的括号化方式得到不同的结果,求所有括号化方式中能得到的最小结果(用指数表示)。
关键观察
1. 合并规则的数学本质
规则 实际上等价于:
- 如果 ,则 (简单的翻倍)
- 如果 ,假设 ,则 ,但向上取整到
这提示我们:小的项会被大的项"吸收",但吸收过程可能产生进位。
2. 问题转化
实际上,这个问题等价于:将区间内的所有数看作二进制位(在对应的指数位置设为1),然后进行二进制加法,但采用特殊的进位规则。
更准确地说,最终结果由区间内的最大值和进位链决定。
3. 重要结论
经过分析(可通过数学归纳法证明),对于区间 ,最小可能的指数结果是:
$$\text{answer} = \max\left(\max_{i=\ell}^r a_i, \lceil \log_2(\text{区间内值为 } M \text{ 的元素个数}) \rceil + M \right) $$其中 。
换句话说:
- 找到区间最大值
- 统计区间内值等于 的元素个数
- 计算 (即容纳 个 所需的最小幂次)
- 答案为 ,但需要处理进位链
4. 进位链的完整分析
实际上,我们需要考虑所有数值的贡献,而不仅仅是最大值。完整的算法是:
- 将区间内所有数按值分组
- 从最小值开始向上处理,模拟进位过程
- 最终得到唯一的幂指数
具体来说,这等价于计算:
$$\text{answer} = \max\left(\left\lceil \log_2 \sum_{i=\ell}^r 2^{a_i} \right\rceil, \max_{i=\ell}^r a_i + 1\right) $$但题目中的特殊规则使得实际结果可能比这个公式稍大。
高效解法
算法思路
对于每个查询 :
- 找到区间最大值
- 统计区间内每个数值的出现次数
- 从 开始向下模拟进位过程,直到没有进位为止
- 最终得到的结果就是最小可能的指数
数据结构设计
由于 ,我们需要高效的数据结构:
- 区间最大值:ST表, 预处理, 查询
- 数值出现次数:由于 ,可以对每个数值维护出现位置列表,然后使用二分统计区间内出现次数
进位模拟算法
int simulate(vector<int>& freq, int start) { int carry = 0; for (int i = start; i >= 0; i--) { int total = freq[i] + carry; if (total == 0) continue; if (total == 1) { return i; // 最终结果就是 2^i } // total >= 2,产生进位 carry = total / 2; if (total % 2 == 1) { return i; // 奇数情况,最终结果就是 2^i } } // 如果一直进位到最后 int result = 0; while (carry > 1) { result++; carry = (carry + 1) / 2; } return result; }完整算法步骤
对于每个查询 :
- 用ST表查询区间最大值
- 初始化频率数组 ,大小
- 对于每个 从最小值到 ,用预处理的列表二分统计 中值为 的个数,存入
- 调用
simulate(freq, M)得到结果
复杂度优化
上述算法对于每个查询需要 时间,最坏 ,无法通过。
关键优化
观察发现:只有出现次数 ≥ 2 的数值才会产生进位影响
因此我们只需要关注:
- 区间最大值
- 所有数值 满足 的
而且进位过程可以快速计算,不需要遍历所有数值。
最终算法
#include <bits/stdc++.h> using namespace std; const int MAX_A = 1000000; int main() { ios::sync_with_stdio(false); cin.tie(0); int n, q; cin >> n >> q; vector<int> a(n); vector<vector<int>> positions(MAX_A + 1); for (int i = 0; i < n; i++) { cin >> a[i]; positions[a[i]].push_back(i); } // 预处理ST表用于区间最大值查询 int logn = __lg(n) + 1; vector<vector<int>> st(n, vector<int>(logn)); for (int i = 0; i < n; i++) { st[i][0] = a[i]; } for (int j = 1; j < logn; j++) { for (int i = 0; i + (1 << j) <= n; i++) { st[i][j] = max(st[i][j-1], st[i + (1 << (j-1))][j-1]); } } auto query_max = [&](int l, int r) { int k = __lg(r - l + 1); return max(st[l][k], st[r - (1 << k) + 1][k]); }; // 处理每个查询 while (q--) { int l, r; cin >> l >> r; l--; r--; int M = query_max(l, r); // 统计每个数值的出现次数(只统计可能产生进位的) map<int, int> freq; // 只关注出现次数 >= 2 的数值,以及最大值 for (int val = 0; val <= M; val++) { if (positions[val].empty()) continue; // 二分统计 [l, r] 区间内 val 的出现次数 auto& pos = positions[val]; int cnt = upper_bound(pos.begin(), pos.end(), r) - lower_bound(pos.begin(), pos.end(), l); if (cnt > 0) { freq[val] = cnt; } } // 模拟进位过程 int result = M; for (int i = M; i >= 0; i--) { if (!freq.count(i)) continue; int total = freq[i]; if (total == 1) { result = max(result, i); } else { // 产生进位 freq[i-1] += total / 2; if (total % 2 == 1) { result = max(result, i); } // 移除当前值,进位到 i-1 freq.erase(i); } } // 处理可能的连续进位 int carry = 0; for (auto& [val, cnt] : freq) { carry = max(carry, cnt); } if (carry > 0) { int additional = 0; while (carry > 1) { additional++; carry = (carry + 1) / 2; } result = max(result, additional); } cout << result << "\n"; } return 0; }进一步优化
对于大数据,还需要更多优化:
- 使用更高效的数据结构统计区间内数值出现次数
- 利用数值范围有限的特点进行优化
- 离线处理查询
总结
这道题的关键在于:
- 理解合并规则的数学本质(二进制进位)
- 发现最终结果只与数值分布有关,与括号化方式无关
- 设计高效的算法模拟进位过程
- 使用合适的数据结构支持快速查询
时间复杂度: 空间复杂度:
- 1
信息
- ID
- 3961
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者