1 条题解

  • 0
    @ 2026-5-17 14:24:29

    题意

    给定长度为 nn 的非负整数数组 aa,将其分割成若干连续非空段。设第 ii 段所有元素的按位或值为 fif_i,要求:

    f1f2fkf_1 \le f_2 \le \cdots \le f_k

    求不同的分割方案数,对 998244353998244353 取模。


    算法

    dp[i]dp[i] 表示以 ii 结尾的合法分割方案数,dp[0]=1dp[0]=1
    prefix[i]=j=0idp[j](modMOD)prefix[i] = \sum_{j=0}^i dp[j] \pmod{MOD}

    对于固定的右端点 ii,随着左端点 llii 向左移动,OR(l,i)OR(l,i) 最多变化 3030 次(因为二进制位只会从 0011 一次)。
    因此可以维护一个 segmentssegments 列表,每个元素为 (orvalue,leftboundary)(or_value, left_boundary),表示:

    • 对于所有 l[left_boundary,next_left1]l \in [left\_boundary, next\_left-1]OR(l,i)=or_valueOR(l,i) = or\_value
    • orvalueor_value 从左到右严格递增。

    转移方法
    对于 segmentssegments 中的第 jj(v,L)(v, L)(其中 LL 是该段最小的 ll),设 RR 为下一段的 L1L-1(最后一段 R=iR=i)。
    j=l1j = l-1 的取值范围是 [L1,R1][L-1, R-1]
    对于这些 jj,以 jj 结尾的上一段的 OR 值一定 v\le v(因为 j+1=lLj+1=l\ge L 且 OR 值随 ll 减小而增大),因此条件自动满足。
    所以该段对 dp[i]dp[i] 的贡献为 $\displaystyle \sum_{j=L-1}^{R-1} dp[j] = prefix[R-1] - prefix[L-2]$。

    segmentssegments 从后向前遍历(即 OR 值从小到大),累加即可得到 dp[i]dp[i]


    复杂度

    • 每个 iisegmentssegments 长度 O(logmaxa)O(\log \max a)
    • 总时间复杂度 O(nlogmaxa)O(n \log \max a)
    • 空间复杂度 O(n)O(n)

    完整代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MOD = 998244353;
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        int n;
        cin >> n;
        vector<int> a(n + 1);
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
        }
    
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        vector<int> prefix(n + 1, 0);
        prefix[0] = 1;
    
        vector<pair<int, int>> segments; // (or_value, left_boundary)
        segments.reserve(30);
    
        for (int i = 1; i <= n; i++) {
            vector<pair<int, int>> new_segments;
            new_segments.emplace_back(a[i], i);
            for (auto [val, left] : segments) {
                int new_val = val | a[i];
                if (new_segments.back().first == new_val) {
                    new_segments.back().second = left;
                } else {
                    new_segments.emplace_back(new_val, left);
                }
            }
            segments = move(new_segments);
    
            int total = 0;
            for (int j = (int)segments.size() - 1; j >= 0; j--) {
                int left = (j == 0 ? 1 : segments[j - 1].second + 1);
                int right = segments[j].second;
                if (left > right) continue;
                int sum = (prefix[right - 1] - (left - 2 >= 0 ? prefix[left - 2] : 0) + MOD) % MOD;
                total = (total + sum) % MOD;
            }
            dp[i] = total;
            prefix[i] = (prefix[i - 1] + dp[i]) % MOD;
        }
    
        cout << dp[n] << "\n";
    
        return 0;
    }
    

    样例解释

    样例1
    输入:

    3
    1 2 3
    

    输出:4
    所有 22=42^{2}=4 种分割均合法。

    样例2
    输入:

    5
    1000 1000 1000 1000 1000
    

    输出:16
    所有 24=162^{4}=16 种分割均合法。

    样例3
    输入:

    3
    3 4 6
    

    输出:3
    合法分割:[3][4][6],[3][4,6],[3,4,6]。

    • 1

    信息

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