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

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

8个月前 (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),兼具效率与简洁性。关键点包括:

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

栈的递增维护特性;

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

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



原创内容 转载请注明出处

分享给朋友:

相关文章

力扣451:ASCII数组计数法 用128个桶解决频率排序问题

力扣451:ASCII数组计数法 用128个桶解决频率排序问题

题目重解给定一个字符串,将字符按照出现频率降序排列。例如输入"tree",可能返回"eetr"或"eert"。题目要求我们不考虑字母顺序,只...

【动态规划入门】力扣509题:斐波那契数列的经典解法与优化思路

【动态规划入门】力扣509题:斐波那契数列的经典解法与优化思路

题目解读‌斐波那契数列是一个经典的数学问题,在计算机科学中常被用作算法教学的入门案例。这个神奇的数列从0和1开始,后续每个数字都是前两个数字之和。题目要求我们计算第n个斐波那契数,看似简单的问题背后却...

2024年GESP五级武器强化(洛谷B4071)解题代码C++版

2024年GESP五级武器强化(洛谷B4071)解题代码C++版

一、题目解读    2024年GESP(青少年软件编程能力等级考试)五级中的“武器强化”(洛谷平台题目编号B4071)是一道典型的算法优化问题。题目要求通过合理...

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

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

牛客12576题解题全解析:动态规划+质因数分解实现跳跃问题最优解

牛客12576题解题全解析:动态规划+质因数分解实现跳跃问题最优解

一、题目解读牛客12576题是一道经典的算法题,要求给定起点N和终点M,求解从N到M的最少跳跃次数。题目考察的核心在于路径优化与动态规划思想,需结合数论中的质因数分解技巧,通过合理设计算法降低时间复杂...

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

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

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

发表评论

访客

看不清,换一张

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