P1950 长方形_NOI导刊2009提高(2)[单调栈][贡献法]
P1950 长方形_NOI导刊2009提高(2)
题意:给你\(n\times m\)的矩形,求没有*
的子矩形数量。\(1\leq n,m\leq 1000\)。
数据比较弱的题目是luoguP1191,\(1\leq n,m \leq 100\)。甚至可以用\(O(n^4)\)水过。
这道题的最优解法\(O(n^2)\)是这样的:
- 枚举子矩形的底边,预处理出每一列可以往上延伸的长度\(h[i]\)。
- 跑两次单调栈,第一次找到每个数右边第一个小于它的数\(l[j]\),第二次找到每个数左边第一个小于等于它的数\(r[j]\)。
- 枚举每一列,那么这个点对答案的贡献就是\((j - l[j] + 1) \times (r[j] - j + 1) \times h[i]\)。
其实枚举的\(j\)就是看看如果这个点是整个大矩形的短板,能够为答案贡献多少个矩形。高度就是从\(1\)到\(h[i]\),共\(h[i]\)个。
两次单调栈要有一次小于等于!同时有多个短板的话可能会算重,我们这样算就不重不漏了。
有一个小细节容易错:在单调栈统计的时候,记录的答案不是那个\(i\),而是相应的\(i-1\)或\(i+1\)。手摸一下就知道了。
代码:
#include<bits/stdc++.h>
const int maxn = 1005;
char ch[maxn][maxn];
int h[maxn];
int g1[maxn], g2[maxn];
int n, m;
long long ans;
void init() {
// print
//for(int i = 1; i <= m; i++) printf("%d ", h[i]);
//printf("\n");
memset(g1, 0, sizeof g1);
memset(g2, 0, sizeof g2);
std::stack<int> sta;// 找到第一个小于我的
for(int i = 1; i <= m; i++) {
while(!sta.empty() && h[sta.top()] > h[i]) {
g2[sta.top()] = i - 1; sta.pop();// 是前面的那个
}
sta.push(i);
}
while(!sta.empty()) {
g2[sta.top()] = m; sta.pop();
}
// print
//for(int i = 1; i <= m; i++) printf("%d ", g2[i]);
//printf("\n");
for(int i = m; i >= 1; i--) {
while(!sta.empty() && h[sta.top()] >= h[i]) {
g1[sta.top()] = i + 1; sta.pop();// 是前面的那个
}
sta.push(i);
}
while(!sta.empty()) {
g1[sta.top()] = 1; sta.pop();
}
// print
//for(int i = 1; i <= m; i++) printf("%d ", g1[i]);
//printf("\n");
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%s", ch[i] + 1);
for(int j = 1; j <= m; j++) {
if(ch[i][j] == '*') h[j] = 0;
else h[j]++;
}
init();
for(int j = 1; j <= m; j++) {
int temp = (j - g1[j] + 1) * (g2[j] - j + 1) * h[j];
// print
//printf("%d ", temp);
ans += temp;
}
//printf("\n");
}
printf("%lld\n", ans);
return 0;
}