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 \le x \le len-1\),此时 \(l \in [1,x]\),贡献为 \(x\)。
- \(len \le x \le Y-i\),此时 \(l \in [x-len+1,x]\),贡献为 \(len\)。
- \(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;
}

浙公网安备 33010602011771号