洛谷P3400题全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),兼具效率与简洁性。关键点包括:
高度数组的继承机制(构建柱状图);
栈的递增维护特性;
左右边界计算的对称逻辑。
该思路适用于类似区域统计问题,为算法竞赛中优化计算提供了经典范例。
原创内容 转载请注明出处






