洛谷P3400题解:单调栈统计全1子矩阵的巧妙方法

一、问题描述
给定一个由0和1组成的n×m矩阵,要求统计所有全1子矩阵的数量。这个问题可以转化为对每个位置计算以其为右下角的子矩阵数量。
二、算法核心思想
三、完整代码实现(带详细注释)
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
const int MAXN = 3005;
int h[MAXN][MAXN]; // 高度数组,记录每个位置向上连续1的个数
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;
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;
// 预处理高度数组:计算每个位置向上连续1的个数
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的个数
将二维问题转化为一维柱状图问题
单调栈操作:
左边界计算:找到每个位置左边第一个比它小的位置
右边界计算:找到每个位置右边第一个比它小的位置
使用栈维护单调递增序列
结果计算:
对每个非零高度位置,计算以其为最高点的子矩阵数量
公式:(当前位置-左边界)×(右边界-当前位置)×高度
五、扩展思考
如何优化算法处理更大的矩阵?
如果要求统计特定形状的子矩阵该如何修改?
这个问题与最大全1子矩阵问题有什么联系?
如何扩展到三维情况?
原创内容 转载请注明出处
