1 条题解

  • 0
    @ 2025-10-27 16:15:21

    题目大意

    给定一个长度为 NN 的序列 A1,A2,,ANA_1, A_2, \ldots, A_N,其中每个元素的值在 11KK 之间。需要回答 QQ 个询问,每个询问 [Li,Ri][L_i, R_i] 要求计算子数组 ALi,ALi+1,,ARiA_{L_i}, A_{L_i+1}, \ldots, A_{R_i} 中不下降子序列的数量,结果对 109+710^9+7 取模。

    注意:空子序列也需要被计算在内。

    问题分析

    关键约束

    1. 数值范围小K20K \leq 20,这是解题的关键
    2. 大规模数据N5×104N \leq 5 \times 10^4Q2×105Q \leq 2 \times 10^5
    3. 不下降子序列:要求 Aj1Aj2AjxA_{j_1} \leq A_{j_2} \leq \ldots \leq A_{j_x}

    基础DP模型

    对于固定区间,经典的DP方法是: 设 dp[i][v]dp[i][v] 表示考虑前 ii 个元素,以值 vv 结尾的不下降子序列数量。

    转移方程:

    $$dp[i][v] = dp[i-1][v] + \sum_{u=1}^v dp[i-1][u] \cdot [A_i = v] $$

    但直接对每个询问进行DP会超时。

    算法设计

    方法一:矩阵表示法 + 线段树

    核心思想:将DP转移用矩阵表示,利用线段树维护区间矩阵乘积。

    矩阵设计

    定义 K×KK \times K 的转移矩阵 MM

    • M[i][j]M[i][j] 表示从以值 ii 结尾转移到以值 jj 结尾的方案数
    • 对于每个位置 tt,构造矩阵 MtM_t
      • 对角线元素 Mt[i][i]=1M_t[i][i] = 1(保持原有状态)
      • 如果 At=jA_t = j,则对于所有 iji \leq jMt[i][j]+=1M_t[i][j] += 1(新增子序列)

    矩阵的性质

    • 初始状态dp0=[1,0,0,,0]dp_0 = [1, 0, 0, \ldots, 0](只有空序列)
    • 转移dpi=dpi1×Midp_i = dp_{i-1} \times M_i
    • 区间查询:$dp_{[L,R]} = dp_{L-1} \times M_L \times M_{L+1} \times \ldots \times M_R$

    线段树应用

    • 每个叶子节点存储对应位置的矩阵 MiM_i
    • 内部节点存储子节点矩阵的乘积
    • 查询 [L,R][L,R] 时,获取该区间的矩阵乘积
    • 用初始向量乘矩阵得到最终DP状态

    方法二:分治预处理

    核心思想:利用分治思想预处理所有可能的区间。

    分治结构

    1. 对序列进行分治,处理每个区间 [l,r][l, r]
    2. 对于每个区间,预处理:
      • 从左端点开始的DP信息
      • 从右端点结束的DP信息
      • 区间内部的DP信息

    合并策略

    • 将区间分成左右两半
    • 合并时考虑跨越中点的子序列
    • 使用Meet in the Middle思想

    方法三:离线处理 + 扫描线

    核心思想:按右端点排序询问,维护以每个值结尾的子序列数量。

    扫描线过程

    1. 将询问按右端点排序
    2. 从左到右扫描序列,维护DP状态
    3. 使用Fenwick树或线段树维护前缀和
    4. 对于每个右端点,回答所有以该点为右端点的询问

    复杂度分析

    矩阵方法

    • 矩阵乘法O(K3)O(K^3)
    • 线段树构建O(NK3)O(NK^3)
    • 每次查询O(K3logN)O(K^3 \log N)
    • 总复杂度O((N+Q)K3logN)O((N+Q)K^3 \log N)

    代入 K=20K=20,在数据范围内可行。

    分治方法

    • 预处理O(NK2logN)O(NK^2 \log N)
    • 每次查询O(K2)O(K^2)
    • 总复杂度O(NK2logN+QK2)O(NK^2 \log N + QK^2)

    扫描线方法

    • 排序O(QlogQ)O(Q \log Q)
    • 处理每个元素O(K2)O(K^2)
    • 总复杂度O(NK2+QlogQ)O(NK^2 + Q \log Q)

    实现细节

    矩阵优化技巧

    1. 稀疏矩阵:转移矩阵是稀疏的,可以优化乘法
    2. 位运算:利用 K20K \leq 20 的特点,使用位运算加速
    3. 模运算:注意 109+710^9+7 取模

    线段树实现

    struct Matrix {
        int mat[K][K];
        Matrix() { /* 初始化单位矩阵 */ }
    };
    
    struct SegmentTree {
        Matrix tree[4 * N];
        
        void build(int node, int l, int r) {
            if (l == r) {
                tree[node] = get_matrix(A[l]);
                return;
            }
            int mid = (l + r) / 2;
            build(2*node, l, mid);
            build(2*node+1, mid+1, r);
            tree[node] = multiply(tree[2*node], tree[2*node+1]);
        }
        
        Matrix query(int node, int l, int r, int ql, int qr) {
            // 区间查询实现
        }
    };
    

    关键洞察

    1. 利用K小的特点K20K \leq 20 使得矩阵方法可行
    2. 结合律性质:DP转移满足结合律,支持区间合并
    3. 空序列处理:初始状态要包含空序列

    算法选择建议

    • 推荐方法一(矩阵+线段树):思路清晰,实现相对简单,复杂度可接受
    • 方法二(分治):常数更小,但实现复杂
    • 方法三(扫描线):理论复杂度最优,但离线处理稍复杂

    总结

    本题的核心在于利用 KK 值较小的特点,将DP转移表示为矩阵形式,然后使用线段树维护区间矩阵乘积来支持高效查询。这种"DP + 矩阵 + 数据结构"的思路在处理区间DP查询问题时非常有效。

    • 1

    「USACO 2020.1 Platinum」Non-Decreasing Subsequences

    信息

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