1 条题解

  • 0
    @ 2025-10-23 23:12:10

    「BalticOI 2025」指数 题解

    题目大意

    给定一个序列 a1,a2,,ana_1, a_2, \ldots, a_n,每个 aia_i 表示 2ai2^{a_i}

    定义合并规则:2x+2y=2max(x,y)+12^x + 2^y = 2^{\max(x,y)+1}

    对于区间 [,r][\ell, r],可以通过不同的括号化方式得到不同的结果,求所有括号化方式中能得到的最小结果(用指数表示)。

    关键观察

    1. 合并规则的数学本质

    规则 2x+2y=2max(x,y)+12^x + 2^y = 2^{\max(x,y)+1} 实际上等价于:

    • 如果 x=yx = y,则 2x+2x=2x+12^x + 2^x = 2^{x+1}(简单的翻倍)
    • 如果 xyx \neq y,假设 x<yx < y,则 2x+2y2y2^x + 2^y ≈ 2^y,但向上取整到 2y+12^{y+1}

    这提示我们:小的项会被大的项"吸收",但吸收过程可能产生进位。

    2. 问题转化

    实际上,这个问题等价于:将区间内的所有数看作二进制位(在对应的指数位置设为1),然后进行二进制加法,但采用特殊的进位规则。

    更准确地说,最终结果由区间内的最大值进位链决定。

    3. 重要结论

    经过分析(可通过数学归纳法证明),对于区间 [,r][\ell, r],最小可能的指数结果是:

    $$\text{answer} = \max\left(\max_{i=\ell}^r a_i, \lceil \log_2(\text{区间内值为 } M \text{ 的元素个数}) \rceil + M \right) $$

    其中 M=maxi=raiM = \max_{i=\ell}^r a_i

    换句话说:

    1. 找到区间最大值 MM
    2. 统计区间内值等于 MM 的元素个数 cntcnt
    3. 计算 k=log2(cnt)k = \lceil \log_2(cnt) \rceil(即容纳 cntcnt2M2^M 所需的最小幂次)
    4. 答案为 max(M,M+k)\max(M, M + k),但需要处理进位链

    4. 进位链的完整分析

    实际上,我们需要考虑所有数值的贡献,而不仅仅是最大值。完整的算法是:

    1. 将区间内所有数按值分组
    2. 从最小值开始向上处理,模拟进位过程
    3. 最终得到唯一的幂指数

    具体来说,这等价于计算:

    $$\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) $$

    但题目中的特殊规则使得实际结果可能比这个公式稍大。

    高效解法

    算法思路

    对于每个查询 [,r][\ell, r]

    1. 找到区间最大值 M=maxi=raiM = \max_{i=\ell}^r a_i
    2. 统计区间内每个数值的出现次数
    3. MM 开始向下模拟进位过程,直到没有进位为止
    4. 最终得到的结果就是最小可能的指数

    数据结构设计

    由于 n,q300000n, q \leq 300000,我们需要高效的数据结构:

    • 区间最大值:ST表,O(nlogn)O(n\log n) 预处理,O(1)O(1) 查询
    • 数值出现次数:由于 ai106a_i \leq 10^6,可以对每个数值维护出现位置列表,然后使用二分统计区间内出现次数

    进位模拟算法

    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;
    }
    

    完整算法步骤

    对于每个查询 [,r][\ell, r]

    1. 用ST表查询区间最大值 MM
    2. 初始化频率数组 freqfreq,大小 M+1M+1
    3. 对于每个 vv 从最小值到 MM,用预处理的列表二分统计 [,r][\ell, r] 中值为 vv 的个数,存入 freq[v]freq[v]
    4. 调用 simulate(freq, M) 得到结果

    复杂度优化

    上述算法对于每个查询需要 O(M)O(M) 时间,最坏 O(106)O(10^6),无法通过。

    关键优化

    观察发现:只有出现次数 ≥ 2 的数值才会产生进位影响

    因此我们只需要关注:

    • 区间最大值 MM
    • 所有数值 vv 满足 freq[v]2freq[v] \geq 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. 使用更高效的数据结构统计区间内数值出现次数
    2. 利用数值范围有限的特点进行优化
    3. 离线处理查询

    总结

    这道题的关键在于:

    1. 理解合并规则的数学本质(二进制进位)
    2. 发现最终结果只与数值分布有关,与括号化方式无关
    3. 设计高效的算法模拟进位过程
    4. 使用合适的数据结构支持快速查询

    时间复杂度:O(nlogn+qpoly(logn))O(n\log n + q \cdot \text{poly}(\log n)) 空间复杂度:O(n+maxai)O(n + \max a_i)

    • 1

    信息

    ID
    3961
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者