纯矩阵计数

洛谷P4147

一句话题意:给一个 \(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;
}
posted @ 2022-04-09 14:21  Faker_yu  阅读(38)  评论(0)    收藏  举报