題解 P1950 【長方形】
阿新 • • 發佈:2021-06-11
考慮列舉一下每一行,把第 \(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; }