1. 程式人生 > 其它 >題解 P1950 【長方形】

題解 P1950 【長方形】

考慮列舉一下每一行,把第 \(i\) 行第 \(j\) 列的不含 “*” 的最高高度記為 \(h_{i,j}\) ,如果把每一行表示成一幅直方圖。

會發現這和某道單調棧的模板題很像,圖也是那兒搬的,可以試著仿照一下做法,維護一個單調棧(單調不降)。

如果棧頂元素大於 \(h_{i,j}\),不停出棧直到棧頂元素不大於 \(h_{i,j}\) 或棧空了,在這個過程中出棧的元素構成了一個單調不降的矩形圖,讓這些矩形的高度同時減去 \(h_{i,j}\),再在新的矩形圖列舉有多少個矩形然後加在最終答案裡,先解釋為什麼要減 \(h_{i,j}\),因為被減去的部分一定會在之後 \(h_{i,j}\)

出棧的時候被算。然後算有多少個矩形的方法就是一個高度一個高度算,方法如下:

如果當前棧頂元素不等於上一個出棧的元素(也就是這是一個新的高度,而不是和之前的高度一樣,如果一樣就是一個矩形了),就算出已經出棧的矩形中能有多少個不同的底,也就是 已經出棧的矩形寬度總和 * (已經出棧的矩形寬度總和-1)/2小學學的等差數列求和,再乘上上一個矩形的高與棧頂元素的差。 總複雜度是 \(O(n^2)\),如果看不太懂的話看程式碼理解吧,不是很長 。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
LL n,m,s[1010][1010];
LL ans;
struct rec{
	LL v,l;
};
struct stack
{
	rec st[1010];
	LL l=0;
	void push(rec x)
	{
		st[++l]=x;
	}
	void pop()
	{
		--l;
	}
	rec top()
	{
		return st[l];
	}
	void clear()
	{
		l=0;
	}
	LL size()
	{
		return l;
	}
}st;//棧
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			char c;cin>>c;
			if(c=='*')s[i][j]=0;
			else s[i][j]=s[i-1][j]+1;//預處理高度
		}

	for(int i=1;i<=n;++i)
	{
		st.clear();
		for(int j=1;j<=m+1;++j)
		{
			LL v=0,l=0;//上一個矩形的高和已經出棧的總寬度
			while(st.size()&&st.top().v>s[i][j])
			{
				if(st.top().v!=v)
					ans+=l*(l+1)*(v-st.top().v)/2;
				l+=st.top().l;
				v=st.top().v;
				st.pop();
			}
			ans+=l*(l+1)*(v-s[i][j])/2;
			st.push({s[i][j],l+1});
		}
	}
	printf("%lld",ans);
	return 0;
}