CF1534F2-Falling Sand (Hard Version)
正题
题目链接:https://www.luogu.com.cn/problem/CF1534F2
题目大意
有一个\(n*m\)个网格,有的网格上有沙子,一个沙子被刷新后会下落到底并且刷新沿途中四周四连通的沙子,你可以选择一些沙子手动刷新。
现在要求第\(i\)列至少有\(a_i\)个沙子下落,求至少手动刷新多少个沙子。
\(1\leq n\times m\leq 4\times 10^5\)
解题思路
显然列要求的\(a_i\)就是要求最下面的那\(a_i\)个沙子被刷新。
手动刷新的肯定都是每一列位置最高的沙子,然后刷新关系可以表示成一张有向图,而且是平面图,那么就说明一个沙子被刷新的条件是手动刷新了某个区间中位置最高的沙子。
我们考虑求出这些区间,先建边,这样最多\(4nm\)条边,然后对于每个沙子要求区间的左端点我们从左往右从最高的沙子开始跑,然后每次走到的点标记删除,右区间就变成从右往左跑。
得到这些区间后我们转换成若干个形如区间\([l,r]\)中必须要有一个\(1\)的限制,然后考虑\(dp\),用单调队列优化一下即可。
时间复杂度:\(O(nm)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define p(x,y) ((((x)-1)*n)+(y))
using namespace std;
const int N=8e5+10;
struct node{
int to,next;
}a[N*4];
int n,m,tot,ls[N],l[N],r[N],lim[N],f[N];
deque<int> q;vector<int> v[N];char s[N];
void addl(int x,int y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;return;
}
void dfsl(int x){
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(l[y])continue;
l[y]=l[x];dfsl(y);
}
return;
}
void dfsr(int x){
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(r[y])continue;
r[y]=r[x];dfsr(y);
}
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
if(s[j]=='#')
v[j].push_back(n-i+1);
}
for(int i=1;i<=m;i++){
if(v[i].empty())continue;
for(int j=0;j<v[i].size()-1;j++){
addl(p(i,v[i][j]),p(i,v[i][j+1]));
if(v[i][j+1]+1==v[i][j])addl(p(i,v[i][j+1]),p(i,v[i][j]));
}
for(int g=-1;g<2;g++){
if(!g||!v[i+g].size())continue;
int z=0,_=0;
while(z<v[i+g].size()&&v[i+g][z]>v[i][0])z++;
for(int h=v[i][0];h>=1;h--){
if(_<v[i].size()-1&&h==v[i][_+1])_++;
if(z<v[i+g].size()&&h==v[i+g][z])
addl(p(i,v[i][_]),p(i+g,v[i+g][z])),z++;
}
}
}
for(int i=1;i<=m;i++){
if(v[i].empty())continue;
int x=p(i,v[i][0]);
if(!l[x])l[x]=i,dfsl(x);
}
for(int i=m;i>=1;i--){
if(v[i].empty())continue;
int x=p(i,v[i][0]);
if(!r[x])r[x]=i,dfsr(x);
}
for(int i=1,x;i<=m;i++){
scanf("%d",&x);
for(int j=v[i].size()-1;j>=(int)v[i].size()-x;j--)
{int y=p(i,v[i][j]);lim[r[y]]=max(lim[r[y]],l[y]);}
}
q.push_back(0);
for(int i=1;i<=m;i++){
f[i]=max(f[i],f[q.front()]+1);
while(!q.empty()&&f[q.back()]>=f[i])q.pop_back();
q.push_back(i);
while(!q.empty()&&q.front()<lim[i])q.pop_front();
}
printf("%d\n",f[q.front()]);
return 0;
}