纯矩阵计数
一句话题意:给一个 \(n \times m\) 的 \(.*\) 矩阵,求全是 \(.\) 的矩阵的个数 \((1 \le n, m \le 10^3\))
做法:设 \(h_{i,j}\) 表示 \(i,j\) 一直向上走直到走不通的距离, \(l_{i,j}\) 表示 \(i,j\) 一直往左走第一个 \(h_{i,k} \le h_{i,j}\) 的 \(F\) 的纵坐标, \(r_{i,j}\) 表示 \(i,j\) 一直往右走第一个 \(h_{i,k} < h_{i,j}\) 的 \(F\) 的纵坐标。
考虑如何统计答案。对一个点 \(i,j\) ,我们找到的 \(l, r\) 肯定是一个“凸”字形,这个点在“凸”肚子上,\(l\) 和 \(r\) 分别是“凸”的两个肩膀( \(l\) 是肩膀和头部相连的右端)。我们要统计的是以第 \(i\) 行为底边,高度为 \(1\) 到 \(h_{i,j}\) 的矩形的个数。这个个数就是 \(h_{i,j}(j - l_{i,j})(r_{i,j} - j)\) 。(高度从1到 \(h_{i,j}\) 取,底边左端点从 \(l_{i,j} + 1\) 到 \(j\) 取,右端点从 \(j\) 到 \(r_{i,j} - 1\) 取)
现在想想怎么求。\(h\) 很好求。$l $ 和 \(r\) 要用单调栈求。单调栈是一种求数列每个数第一个(不)大于/小于该数的位置的数据结构。将序列元素按顺序依次做如下事情:先依次弹出比他优先级小的栈中元素,当前元素一定就是这些元素最近的符合要求的点,更新答案;然后把当前元素入栈。遍历完后,剩下的元素一定是从栈底到栈顶按优先级递减的,所以它们的答案都是 \(n + 1 / 0\) ;
再说正确性问题。由于一边是 \(<\) ,一边是 \(\le\) ,所以不重不漏。如果都是 \(\le\) 会重,都是 \(<\) 会漏。具体为什么这样就对了不太清楚,大概是一边算了另一边就不会算,不过好像很多去重的小技巧都是这个。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
bool mp[N][N];
int l[N][N], r[N][N], h[N][N];
int stk[N], top;
int n, m;
long long res;
char v;
inline int read()
{
int x = 0;
while(!isdigit(v)) v = getchar();
while(isdigit(v)) x = (x << 1) + (x << 3) + v - 48, v = getchar();
return x;
}
int main()
{
n = read(); m = read();
for(int i = 1; i <= n; ++i)
for(int j = 1 ; j <= m; ++j)
{
while((v ^ '*') && (v ^ '.')) v = getchar();
mp[i][j] = (v ^ '*') ? true : false;
v = getchar();
}
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
if(mp[i][j])
h[i][j] = h[i - 1][j] + 1;
for(int i = 1; i <= n; ++i)
{
top = 0;
for(int j = m; j; --j)
{
while(top && h[i][j] <= h[i][stk[top]])
{
l[i][stk[top]] = j;
--top;
}
stk[++top] = j;
}
while(top)
{
l[i][stk[top]] = 0;
--top;
}
}
for(int i = 1; i <= n; ++i)
{
top = 0;
for(int j = 1; j <= m; ++j)
{
while(top && h[i][j] < h[i][stk[top]])
{
r[i][stk[top]] = j;
--top;
}
stk[++top] = j;
}
while(top)
{
r[i][stk[top]] = m + 1;
--top;
}
}
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
res += h[i][j] * (j - l[i][j]) * (r[i][j] - j);
cout << res;
return 0;
}

浙公网安备 33010602011771号