【题解】洛谷 P7274 草地 | 20211008 模拟赛【最小生成树 单调栈 LCT】
题意
网格上有若干黑色格子。有两种技能,分别可以把所有黑色格子向【左/右】或【上/下】的方向扩展一格,两种技能第 \(i\) 次分别要用 \(a_i,b_i\) 的代价(洛谷原题代价为 1)。用最小的代价使所有黑色格子八连通。\(n,m\leq 1000\)。
题解
多次技能之后,一个黑色格子会被放大到一个矩形,因此我们容易求出(单独考虑)两个黑色格子要连通需要的横向技能个数和纵向技能个数,接着可以连边求一个横向代价 max 加纵向代价 max 最小的生成树。暴力连边是 \(n^4\) 的,但是我们可以用单调栈连边,连出一个平面图,边数 \(O(n^2)\)。接着依次加横向代价从小到大的边,求纵向代价的最小生成树,用 LCT 维护。
#include<bits/stdc++.h>
using namespace std;
int getint(){ int x;scanf("%d",&x);return x; }
mt19937 mt;
const int N=1010;
#define ll long long
char s[N][N];
int n,m,id[N][N],cnt,p[N*N],q[N*N],x[N][N];
vector<pair<int,int>>e;
ll v[N],w[N];
void calc(){
memset(x,0,sizeof(x));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
x[i][j]=s[i][j]=='1'?i:x[i-1][j];
for(int i=1;i<=n;i++){
vector<int>stk;
for(int j=1;j<=m;j++){
if(!x[i][j])continue;
if(s[i][j]=='1'){
for(int k:stk)
e.emplace_back(id[i][j],id[x[i][k]][k]);
}
while(stk.size()&&x[i][stk.back()]<=x[i][j])stk.pop_back();
stk.push_back(j);
}
}
}
namespace LCT{
const int N=3e6+10;
int eu[N],ev[N],ew[N];
bool qaq[N];
struct node{
int ch[2],id,mx,fa;
bool rev;
};
node t[N];
inline void pushdown(int x){
if(t[x].rev){
swap(t[x].ch[0],t[x].ch[1]);
t[t[x].ch[0]].rev^=1;
t[t[x].ch[1]].rev^=1;
t[x].rev=0;
}
}
inline void pushup(int x){
if(t[x].ch[0])t[t[x].ch[0]].fa=x;
if(t[x].ch[1])t[t[x].ch[1]].fa=x;
t[x].mx=x;
if(ew[t[t[t[x].ch[0]].mx].id]>ew[t[t[x].mx].id])t[x].mx=t[t[x].ch[0]].mx;
if(ew[t[t[t[x].ch[1]].mx].id]>ew[t[t[x].mx].id])t[x].mx=t[t[x].ch[1]].mx;
}
inline bool isroot(int x){ return t[t[x].fa].ch[0]!=x&&t[t[x].fa].ch[1]!=x; }
inline bool which(int x){ return t[t[x].fa].ch[1]==x; }
inline void rotate(int x){
int f=t[x].fa,ff=t[f].fa;
bool w=which(x);
if(!isroot(f))t[ff].ch[which(f)]=x;
t[f].ch[w]=t[x].ch[w^1];
t[x].ch[w^1]=f;
t[x].fa=ff;
pushup(x);
pushup(f);
if(!isroot(x))pushup(ff);
}
int stk[N];
inline void splay(int x){
int y=x,top=0;
while(!isroot(y))stk[++top]=y,y=t[y].fa;
stk[++top]=y;
for(int i=top;i;--i)pushdown(stk[i]);
while(!isroot(x)){
int f=t[x].fa;
if(!isroot(f)){
if(which(x)==which(f))rotate(f);
else rotate(x);
}
rotate(x);
}
}
inline void access(int x){
for(int y=0;x;y=x,x=t[x].fa)
splay(x),t[x].ch[1]=y,pushup(x);
}
inline int getroot(int x){
access(x);
splay(x);
while(pushdown(x),t[x].ch[0])x=t[x].ch[0];
return x;
}
inline void toroot(int x){
access(x);
splay(x);
t[x].rev^=1;
}
inline void link(int x,int y){
toroot(x);toroot(y);
t[x].fa=y;
}
inline void cut(int x,int y){
toroot(x);
access(y);
splay(y);
t[y].ch[0]=t[x].fa=0;
pushup(y);
}
int cnt;
multiset<int>eee;
void new_edge(int i){
int u=eu[i],v=ev[i],w=ew[i];
if(getroot(u)!=getroot(v)){
++cnt;
t[cnt].id=i;
t[cnt].mx=cnt;
link(u,cnt);
link(v,cnt);
eee.insert(w);
return;
}
toroot(u);
access(v);
splay(v);
int c=t[v].mx;
if(ew[t[c].id]<=w)return;
eee.erase(eee.find(ew[t[c].id]));
cut(c,eu[t[c].id]);
cut(c,ev[t[c].id]);
t[c].id=i;
pushup(c);
link(u,c);
link(v,c);
eee.insert(w);
}
}
vector<int>ee[N];
int main(){
n=getint(),m=getint();
for(int i=1;i<=m;i++)v[i+1]=v[i]+getint();
for(int i=1;i<=n;i++)w[i+1]=w[i]+getint();
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]=='1')
id[i][j]=++cnt,p[cnt]=i,q[cnt]=j;
calc();
for(int i=1;i<=n;i++){
reverse(id[i]+1,id[i]+m+1);
reverse(s[i]+1,s[i]+m+1);
}
for(int i=1;i<=cnt;i++)
q[i]=m+1-q[i];
calc();
for(int i=1;i<=m;i++){
int y=0;
for(int j=1;j<=n;j++)
if(s[j][i]=='1'){
if(y)e.emplace_back(id[j][i],id[y][i]);
y=j;
}
}
LCT::cnt=cnt;
for(int i=0;i<e.size();i++){
LCT::eu[i+1]=e[i].first;
LCT::ev[i+1]=e[i].second;
LCT::ew[i+1]=abs(p[e[i].first]-p[e[i].second])+1;
ee[abs(q[e[i].first]-q[e[i].second])].push_back(i+1);
}
long long ans=1e18;
for(int i=0;i<=m;i++){
for(int j:ee[i])
LCT::new_edge(j);
if(LCT::eee.size()+1<cnt)continue;
else ans=min(ans,v[i]+w[*prev(LCT::eee.end())-1]);
}
cout<<ans<<endl;
}