CSES1448-Maximum Building II

Description

传送门
给你一个 \(n\times m\) 的森林地图,其中一些方格是空的,一些方格有树木。\(n,m \le 1000\)
你想要在森林中放置一个 \(r \times c\) 的矩形建筑(\(1 \le r \le n,1 \le c \le m\))使得不需要砍伐任何树木。对于每对 \((r,c)\),计算有多少种放置方式。

Solution

整了一个思维难度较低的做法。

不是很会枚举右端点。考虑逐行扫描 + 单调栈,定义 \(h_i\) 为当前行第 \(i\) 列空白的最大高度,用单调栈维护 \(h\) 的后缀(严格)最小值。
设当前位于下标 \(cur\),对单调栈里的后缀最小值 \(h_i\),可以 \(O(1)\) 预处理 \([L_i,R_i]\) 表示 \([L_i,cur]\)\([R_i,cur]\) 这些区间的最小值均为 \(h_i\)。(事实上 \(R_i=i\)
可以对每个下标 \(cur\) 枚举所有的后缀最小值 \(h_i\) 计算以 \(cur\) 为右边界的矩形(矩形高范围此时已确定),枚举矩形的左边界,那么对所有 \(r \in [1,h_i]\)\(c \in [cur-L_i+1,cur-R_i+1]\)\((r,c)\) 均有 \(1\) 的贡献。使用二维差分即可做到 \(O(nm^2)\)
给出代码实现:

top=0;
for(int j=1;j<=m;j++)
{
  while(top&&h[st[top]]>=h[j])top--;
  L[j]=st[top]+1,R[j]=j;
  st[++top]=j;
  for(int k=1;k<=top;k++)if(h[st[k]])ds[0].upd(1,h[st[k]],j-st[k]+1,j-L[st[k]]+1,1);
}

考虑拆出每个后缀最小值对答案的贡献,发现每次矩形加的行不变,列则类似滑动窗口向右移动。不妨在后缀最小值被 pop 出栈时计算答案。
对后缀最小值 \(i\),令 \(len=R_i-L_i+1\),将 \(i\) pop 出栈的下标为 \(Y\)。设 \(l\) 为矩形加最左边的一列,则有 \(1 \le l \le Y-i\)
对每一列 \(x \in [1,Y-i+len-1]\) 考虑 \(i\) 的贡献,可以被计入答案的 \(l\) 需要满足 \(x-len+1 \le l \le x\)。分情况讨论:(只给出 \(len \le Y-i\) 的情况,\(len >Y-i\) 的情况见代码)

  1. \(1 \le x \le len-1\),此时 \(l \in [1,x]\),贡献为 \(x\)
  2. \(len \le x \le Y-i\),此时 \(l \in [x-len+1,x]\),贡献为 \(len\)
  3. \(Y-i \le x \le Y-i+len-1\),此时 \(l \in [x-len+1,Y-i]\),贡献为 \(Y-i+len-x\)
    维护两个差分数组分别维护列的系数与常数项即可,时空复杂度 \(O(nm)\)

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int h[N];
 
struct dlt
{
    int pre[N][N];
    void clear()
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)pre[i][j]=0;
    }
    void upd(int u,int d,int l,int r,int v)
    {
        d++,r++;
        pre[u][l]+=v;
        pre[u][r]-=v;
        pre[d][l]-=v;
        pre[d][r]+=v;
    }
    void solve()
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                pre[i][j]+=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1];
    }
}ds[2];//差分数组
 
int st[N],top;
int L[N],R[N];
 
void update(int i,int Y)  //未知数与文字部分意义一致,可放心食用
{
    if(!h[i])return;
    int len=R[i]-L[i]+1;
    if(len<=Y-i)
    {
        ds[1].upd(1,h[i],1,len-1,1);
        ds[0].upd(1,h[i],len,Y-i,len);
        ds[0].upd(1,h[i],Y-i+1,Y-i+len-1,Y-i+len);
        ds[1].upd(1,h[i],Y-i+1,Y-i+len-1,-1);
    }
    else
    {
        ds[1].upd(1,h[i],1,Y-i,1);
        ds[0].upd(1,h[i],Y-i+1,len-1,Y-i);
        ds[0].upd(1,h[i],len,Y-i+len-1,Y-i+len);
        ds[1].upd(1,h[i],len,Y-i+len-1,-1);
    }
}
int main()
{
    cin>>n>>m;
    ds[0].clear();
    ds[1].clear();
    for(int i=1;i<=n;i++)
    {
        string s;
        cin>>s;
        for(int j=1;j<=m;j++)h[j]=(s[j-1]=='*')?0:h[j]+1;
        top=0;
        for(int j=1;j<=m;j++)
        {
            while(top&&h[st[top]]>=h[j])
            {
                update(st[top],j);
                top--;
            }
            L[j]=st[top]+1,R[j]=j;
            st[++top]=j;
           // for(int k=1;k<=top;k++)if(h[st[k]])ds[0].upd(1,h[st[k]],j-st[k]+1,j-L[st[k]]+1,1);
        }
       while(top) //注意要把剩的后缀最小值计入答案
        {
            update(st[top],m+1);
            top--;
        }
    }
    ds[0].solve(),ds[1].solve();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)cout<<ds[0].pre[i][j]+ds[1].pre[i][j]*j<<" ";
        cout<<"\n";
    }
    return 0;
}
posted @ 2025-11-26 23:45  Antares_qwq  阅读(17)  评论(0)    收藏  举报