题解:P12019 [NOISG 2025 Finals] 洪水
注:下文所有的复杂度分析均认为
思路
首先可以发现,最后被淹没的形状一定是一个矩形,否则一定存在一个点有两个相邻的被淹没的点。
考虑观察这个矩形有什么性质,发现矩形一定是被一圈 1 包起来的(但是外围的四个角可以不用管),且矩形内部不能存在一行或一列全为 1。
考虑设
-
-
-
-
- 不存在一个
i\in(x_1,x_2) 使得rt_{i,y_1}+1\ge y_2 ; - 不存在一个
i\in(y_1,y_2) 使得dn_{x_1,i}+1\ge x_2 。
现在固定左上角,考虑从后两个限制入手。发现若一个
接下来考虑前面四条限制,对于一个
分析一下每一行会扫到的
接下来考虑统计答案,对于每个点,最后形成的联通块相当于覆盖到这个点面积最小的矩形。此时做法就很多了,可以使用扫描线+标记永久化线段树,每个线段树节点维护一个可删堆即可。
时间复杂度 但是这么写有概率会被卡常,对于面积和小的跑暴力,面积和大的跑扫描线。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5005;
int n,m,dn[N][N],rt[N][N],stk[N][N],top[N];
int l1[N*N],r1[N*N],l2[N*N],r2[N*N],s[N*N],cnt;
long long ans = 0;
char ch[N][N];
namespace sub1{
int id[N*N],a[N][N];
inline void main()
{
for(int i = 1;i<=cnt;i++) id[i] = i;
sort(id+1,id+cnt+1,[&](int x,int y){return s[x]>s[y];});
for(int i = 1;i<=cnt;i++)
{
int now = id[i];
for(int x = l1[now];x<=r1[now];x++)
for(int y = l2[now];y<=r2[now];y++)
a[x][y] = s[now];
}
for(int i = 1;i<=n;i++)
for(int j = 1;j<=m;j++)
if(ch[i][j]=='0') ans+=a[i][j];
cout<<ans;
}
}
namespace sub2{
vector<pair<pair<int,int>,int>> vec[N];
priority_queue<int> q[N<<2],del[N<<2];
#define ls (k<<1)
#define rs (k<<1|1)
void change(int k,int l,int r,int x,int y,int v)
{
if(l>y||r<x) return;
if(l>=x&&r<=y)
{
if(v<0)
{
del[k].push(v);
while(!q[k].empty()&&!del[k].empty()&&q[k].top()==del[k].top()) q[k].pop(),del[k].pop();
}
else q[k].push(-v);
return;
}
int mid = (l+r)/2;
change(ls,l,mid,x,y,v),change(rs,mid+1,r,x,y,v);
}
void ask(int k,int l,int r,int x,int mn)
{
if(!q[k].empty()) mn = min(mn,-q[k].top());
if(l==r)
{
if(ch[x][l]=='0') ans+=mn;
return;
}
int mid = (l+r)/2;
ask(ls,l,mid,x,mn),ask(rs,mid+1,r,x,mn);
}
inline void main()
{
for(int i = 1;i<=cnt;i++)
{
vec[l1[i]].push_back({{l2[i],r2[i]},s[i]});
vec[r1[i]+1].push_back({{l2[i],r2[i]},-s[i]});
}
for(int i = 2;i<n;i++)
{
for(auto _:vec[i])
{
int l = _.first.first,r = _.first.second,v = _.second;
change(1,2,m-1,l,r,v);
}
ask(1,2,m-1,i,2e9);
}
cout<<ans;
}
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
n+=2,m+=2;
for(int i = 2;i<n;i++)
for(int j = 2;j<m;j++)
cin>>ch[i][j];
for(int i = 1;i<=n;i++) ch[i][1] = ch[i][m] = '1';
for(int i = 1;i<=m;i++) ch[1][i] = ch[n][i] = '1';
for(int i = n;i;i--)
for(int j = m;j;j--)
{
if(ch[i][j]=='1')
{
if(ch[i+1][j]=='1') dn[i][j] = dn[i+1][j];
else dn[i][j] = i;
if(ch[i][j+1]=='1') rt[i][j] = rt[i][j+1];
else rt[i][j] = j;
}
else
{
if(ch[i+1][j]=='1') dn[i][j] = dn[i+1][j];
if(ch[i][j+1]=='1') rt[i][j] = rt[i][j+1];
}
}
long long sum = 0;
for(int i = n;i;i--)
{
top[0] = 0;
for(int j = m;j;j--)
{
int las1 = 0;
while(top[0])
{
int now = stk[0][top[0]];
while(top[j])
{
int k = stk[j][top[j]];
if(rt[k][j]>rt[i][j]||rt[k][j]+1>=now) break;
top[j]--;
}
int k = stk[j][top[j]];
if(top[j]&&now<=min(rt[i][j],rt[k][j])+1&&k<=min(dn[i][j],dn[i][now])+1&&k>dn[i][las1])
{
if(i+1<k&&j+1<now)
{
int w = (k-i-1)*(now-j-1);
sum+=w;
cnt++;
s[cnt] = w,l1[cnt] = i+1,r1[cnt] = k-1,l2[cnt] = j+1,r2[cnt] = now-1;
}
}
if(dn[i][now]>dn[i][j]) break;
las1 = stk[0][top[0]];
top[0]--;
}
stk[0][++top[0]] = j;
while(top[j]&&rt[stk[j][top[j]]][j]<=rt[i][j]) top[j]--;
stk[j][++top[j]] = i;
}
}
if(sum<=3e8) sub1::main();
else sub2::main();
return 0;
}