1. 程式人生 > >洛谷1950 長方形 (單調棧)

洛谷1950 長方形 (單調棧)

懶得放題目連結了
qwq

(果然我是菜的真實,單調棧都不會,gg)

首先我們看到這個題。
應該會想到就是直接列舉行,然後計算當前行的答案。

那現在,對於每一行來說,如果我們能夠維護出\(h[j]\)表示第\(j\)列的最近的一個不合法的位置。
那麼實際上就是求一堆矩形的並的一個圖形中。
有多少個矩形。

首先考慮暴力,我們可以直接列舉每一列,然後列舉他前面的列進行計算,這個複雜度是\(O(n^3)\)的。
那我們應該怎麼去優化他呢?

這時候就需要單調棧了!

我們用單調棧維護一個單調上升的序列。

通過列舉每個列,算以\((i,j)\)這個點為右下角的\(ans\)

然後我們通過維護一個\(now\)

來表示已經處理過的值,每次更新
然後\(ans+=now\)

具體應該怎麼做呢?
對於每次加入,我們嘗試彈出棧裡面的元素,然後刪除他和他之前那個元素的位置之間的貢獻(也就是他們兩個之間的距離乘上棧頂元素的高度!)
(其實就相當於中間一段矩陣都是不合法的,沒法選)

最後加入的時候,記得加入當前列與棧頂之間的貢獻(就是當前列和棧頂元素之間的距離乘上當前列的\(i-h[j]\)
表示中間一段矩形都是可以算的。‘

這裡運用了一個很巧妙的思路就是每次只處理相鄰兩個元素的貢獻

具體實現還是看程式碼吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
using namespace std;
inline int read()
{
   int x=0,f=1;char ch=getchar();
   while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
   while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
   return x*f;
}
const int maxn = 1e6+1e2;
struct Node
{
  int pos,val;  
};
Node a[maxn];
int n,m;
char s[3010][3010];
int h[maxn];
long long ans,now;
int main()
{
  n=read(),m=read();
  for (int i=1;i<=n;i++) scanf("%s",s[i]+1);
  for (int i=1;i<=n;i++)
  {
     //if (s[i][j]=='*') h[j]=i;
     int top=0;
     now=0;
     for (int j=1;j<=m;j++)
     {
        if (s[i][j]=='*') h[j]=i; //維護每一列的最近的一個不合法點在哪裡 
        while (top>=1 && a[top].val<=h[j]) //之所以是>=,是因為h表示點的位置,越小反而越高  
        {
            now-=(a[top].pos-a[top-1].pos)*(i-h[a[top].pos]); //每次減去這一部分的答案 
            top--;
        }
        now+=(j-a[top].pos)*(i-h[j]);///計算當前元素和棧頂之間對ans的貢獻 
        a[++top].val=h[j];
        a[top].pos=j;
        ans+=now;
     }
  }
  cout<<ans;
  return 0;
}