当前位置:首页 > 洛谷 > 洛谷P3400题全1子矩阵计数算法解析:动态规划与栈优化解题实践

洛谷P3400题全1子矩阵计数算法解析:动态规划与栈优化解题实践

3个月前 (07-20)

洛谷P3400题全1子矩阵计数算法解析:动态规划与栈优化解题实践 洛谷题解 动态规划 栈结构 C++ 第1张

一、题目解读

洛谷P3400题要求计算一个由0和1构成的二维矩阵中,所有全1子矩阵的数量。全1子矩阵指由连续1组成的矩形区域,无0元素。题目核心在于高效统计所有满足条件的子矩阵,需要兼顾时间复杂度和空间优化,属于经典动态规划数据结构结合的算法题。

二、解题思路

采用“逐行扫描+优化”的策略。关键在于将二维问题转化为一维:每行视为独立高度数组,利用栈计算每个1元素的左右边界,进而通过边界差与高度乘积求得单行子矩阵数,最终累加各层结果。核心逻辑如下:

1. 预处理高度数组:逐行扫描,若当前为1,则继承上一行高度+1,形成柱状结构。

2. 单行求解:利用栈维护递增高度序列,动态计算每个1元素的左右最远可达边界(即两侧首个更矮元素位置)。

3. 面积计算:子矩阵面积 = (右边界 - 左边界) × 高度 × 宽度(即当前高度),累加所有有效区域。

三、解题步骤

1. 预处理高度数组:通过嵌套循环读取输入,若字符为'1',则h[i][j] = h[i-1][j] + 1,构建柱状图表示每列连续1的高度。

2. 逐行计算子矩阵数:

调用solveRow(row)函数:

    a. 左边界计算:栈中维护递增高度,若新元素更矮,则弹出栈顶直至满足递增,当前左边界为栈顶元素位置。

    b. 右边界同理反向遍历,确保边界严格最远。

    c. 遍历每列,计算面积并累加。

3. 总结果累加:循环n行,累加每行子矩阵数,输出最终答案。

四、代码与注释

#include <iostream>
#include <stack>
#include <vector>
using namespace std;

const int MAXN = 3005;
int h[MAXN][MAXN]; // 高度数组
int n, m;

// 计算单行中的全1子矩阵数
long long solveRow(int row) {
    stack<int> st;
    long long res = 0;
    vector<int> left(m+2), right(m+2);
    
    // 左边界计算
    for(int j = 1; j <= m; j++) {
        while(!st.empty() && h[row][st.top()] >= h[row][j]) {
            st.pop(); // 弹出更高元素,直至找到左边界
        }
        left[j] = st.empty()? 0 : st.top(); // 记录左边界位置
        st.push(j);
    }
    
    // 清空栈
    while(!st.empty()) st.pop();
    
    // 右边界计算(反向遍历)
    for(int j = m; j >= 1; j--) {
        while(!st.empty() && h[row][st.top()] > h[row][j]) {
            st.pop();
        }
        right[j] = st.empty()? m+1 : st.top();
        st.push(j);
    }
    
    // 计算结果
    for(int j = 1; j <= m; j++) {
        if(h[row][j] == 0) continue; // 跳过0元素
        res += (long long)(j - left[j]) * (right[j] - j) * h[row][j];
    }
    return res;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    long long ans = 0;
    
    // 预处理高度数组
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            char c; cin >> c;
            if(c == '1') h[i][j] = h[i-1][j] + 1; // 继承高度
            else h[i][j] = 0;
        }
    }
    
    // 逐行计算
    for(int i = 1; i <= n; i++) {
        ans += solveRow(i);
    }
    
    cout << ans << endl;
    return 0;
}

五、总结

本解法通过“动态规划预处理+栈优化边界计算”高效解决全1子矩阵计数问题。核心在于将二维搜索转化为一维扫描,利用栈维护单调性快速定位边界,避免暴力枚举。时间复杂度O(nm),空间复杂度O(m),兼具效率与简洁性。关键点包括:

高度数组的继承机制(构建柱状图);

栈的递增维护特性;

左右边界计算的对称逻辑。

该思路适用于类似区域统计问题,为算法竞赛中优化计算提供了经典范例。



原创内容 转载请注明出处

分享给朋友:

相关文章

力扣144:递归之美 轻松掌握二叉树前序遍历

力扣144:递归之美 轻松掌握二叉树前序遍历

题目解读二叉树的前序遍历是一种基础但重要的树遍历方式,其遍历顺序为:先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树。给定一个二叉树的根节点,我们需要按照这个顺序访问所有节点,并将它们...

力扣965题深度解析:单值二叉树的判断技巧

力扣965题深度解析:单值二叉树的判断技巧

重新解读题目 判断一棵二叉树是否为“单值二叉树”,即所有节点的值是否完全相同。题目看似简单,实则考验对树结构递归特性的理解。若一棵树的所有节点值相同,其必然满足:根节点与左右子树的值一致,且...

力扣540题:线性扫描法如何高效定位唯一数

力扣540题:线性扫描法如何高效定位唯一数

题目重解一个严格递增的有序数组中,除某个元素外,其余每个元素均出现两次。这个看似简单的条件背后隐藏着巧妙的规律——单一元素会打破数组的"成对对称性"。题目要求以O(log n)时间...

GESP2023年六级真题解析:动态规划解决小杨买饮料问题(洛谷3873)

GESP2023年六级真题解析:动态规划解决小杨买饮料问题(洛谷3873)

一、题目解读小杨买饮料是GESP 2023年六级认证考试中的一道经典动态规划题目,考察学生对背包问题的理解和应用能力。题目描述小杨需要购买n种饮料,每种饮料有特定的体积w和价格v,他要在不超过容量l的...

NOIP 2008火柴棒等式题解(C++代码实现)  动态规划与枚举算法详解

NOIP 2008火柴棒等式题解(C++代码实现) 动态规划与枚举算法详解

一、题目解读火柴棒等式问题(NOIP 2008,洛谷P1149)要求使用给定数量的火柴棒,构造形如 A + B = C 的等式,其中A、B、C均为整数,且火柴棒总数恰好等于输入值。需统计符合条件的等式...

2024蓝桥杯省赛B组“传送阵”题解(C++代码+图论算法优化)

2024蓝桥杯省赛B组“传送阵”题解(C++代码+图论算法优化)

一、题目解读2024年蓝桥杯省B组“传送阵”题目要求处理一个包含n个节点的图,节点间存在单向传输关系。每个节点i可传送至a[i]指定的节点,形成可能存在的环结构。题目需求解从任意节点出发能到达的最长路...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。