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

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

6小时前

洛谷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),兼具效率与简洁性。关键点包括:

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

栈的递增维护特性;

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

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



原创内容 转载请注明出处

分享给朋友:

相关文章

力扣第71题:用栈轻松解决Unix路径简化问题

力扣第71题:用栈轻松解决Unix路径简化问题

题目解读:在Unix风格的文件系统中,我们经常需要处理各种复杂的路径表示。给定一个绝对路径字符串,我们需要将其转换为最简化的规范路径。规范路径要求:路径始终以斜杠'/'开头;两个目录名...

手搓顺序表类代码注释与详解:从零实现动态数组(新手教程)

一、简介和特点顺序表(Sequential List)是数据结构中基础的一种线性表,其特点是将数据元素存储在连续的内存空间中。通过数组实现,支持随机访问(即通过索引直接访问元素),适用于频繁随机读取的...

LeetCode 120题三角形最小路径和最优解法:动态规划详解与代码实现

LeetCode 120题三角形最小路径和最优解法:动态规划详解与代码实现

一、题目解读LeetCode 120题“三角形最小路径和”要求给定一个由数字组成的三角形,从顶部开始向下移动,每次可向左或向右移动一格,计算从顶至底的最小路径和。三角形以二维向量形式给出,每层元素数量...

CSP-J 2019公交换乘题解析:基于队列优化的动态规划代码详解

CSP-J 2019公交换乘题解析:基于队列优化的动态规划代码详解

一、题目解读CSP-J 2019年的“公交换乘”题目(洛谷P5661)要求模拟地铁与公交交替出行的费用计算。题目核心在于地铁消费会产生优惠券,而公交可在45分钟内使用优惠券抵扣车费。需要处理n条出行记...

【蓝桥杯2015省赛解析】生命之树:树形DP解题全攻略(洛谷P8625代码详解)

【蓝桥杯2015省赛解析】生命之树:树形DP解题全攻略(洛谷P8625代码详解)

一、题目解读    “生命之树”是一道经典的树形结构问题,要求计算一棵带权树中,以某个节点为根的最大子树权值和。题目输入为n个节点及边信息,每个节点有权值wi,...

洛谷2789题解:直线交点数的递归求解与优化(附代码详解)

洛谷2789题解:直线交点数的递归求解与优化(附代码详解)

一、题目解读洛谷2789题要求计算n条直线在平面上两两相交时产生的不同交点数量。题目强调“不同”交点,需排除重复情况。解题关键在于如何高效枚举所有可能的交点组合,并避免重复计数。二、解题思路参考代码采...

发表评论

访客

看不清,换一张

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