LOJ6698 一键挖矿
一键挖矿
2098 年 4 月,Terraria 发布了 1.7 版本更新。
更新日志中显示,这个版本添加了一种新的机关,占用 \(n\times m\) 的矩形区域。将这个区域中第 \(i\) 行第 \(j\) 列的方块记作 \(\left<i,j\right>\)。
每个方块有一个在 \(1\) 到 \(nm\) 之间的权值,记作 \(w_{\left<i,j\right>}\),所有方块的权值互不相同。你可以选定两个参数 \(l,r\),满足 \(1\le l\le r\le nm\)。在此参数作用下,所有权值在 \([l,r]\) 外的方块将会虚化,只留下所有权值在 \([l,r]\) 内的方块。形式化地说,一个方块 \(\left<i,j\right>\) 会被保留当且仅当 \(l\le w_{\left<i,j\right>}\le r\)。
你发现 1.7 版本仍兼容七十年前已停止更新的 tModLoader v1.23.7。你高兴地载入修改日期为 2020/9/21 19:44 的 VeinMiner.tmod 一键挖矿 Mod,想要试试它能不能对新的机关起作用。
一键挖矿 Mod 可以一次性采集所有与初始挖掘方块四连通的未虚化的方块。也就是说,可以利用这个 Mod 采集所有的与初始挖掘方块在同一四连通块内的方块。
但是因为 Terraria 1.7 对方块更新进行了优化,所以这个 Mod 有一个 bug:如果所有与初始挖掘方块四连通的方块没有形成一个矩形区域,则无法完整地把这些方块全部采集下来。
你想知道有多少种选择参数 \(l,r\) 的方法,使得在参数作用下,能够使用一键挖矿 Mod 在不触发 bug 的情况下一次性采集所有未虚化的机关方块。
对于所有测试点:\(1\le w_{\left<i,j\right>}\le nm\le2\times10^5\)。
题解
https://jklover.hs-blog.cf/2020/07/15/Loj-6698-一键挖矿/#more
线段树.
一维时是经典问题,但我们常用的单调栈 + 线段树做法并不能比较方便的搬到二维上,需要考虑另外一种条件转化.
加入权值在区间 \([l,r]\) 内的格子,令它们的颜色为黑色,其它的格子颜色为白色.
考虑所有的 \((n+1)\times (m+1)\) 个 \(2\times 2\) 的小正方形(超出边界也算),则所有黑色格子形成一个矩形,当且仅当恰好有 \(4\) 个小正方形内部有 \(1\) 个黑色格子,并且没有任何一个小正方形内部有 \(3\) 个黑色格子.
必要性是显然的,任何一个由黑色格子组成的矩形都满足以上条件.充分性可以这样考虑,初始时一定是有 \(4\) 个黑色格子,要求恰好有 \(4\) 个小正方形内部有 \(1\) 个黑色格子,就必须用黑色格子将它们连起来,形成矩形的边界,而此时角的地方会出现包含 \(3\) 个黑色格子的小正方形,只有将内部全部填满后才会消失,于是可以得出这个条件是充分必要的.
有了这个结论,再来考虑如何计算答案.我们从小到大枚举 \(r\) ,并对每个 \(l\le r\) 维护 \(f(l)\) ,表示将权值在 \([l,r]\) 内的格子染黑后,有多少个小正方形内部有 \(1\) 个或 \(3\) 个黑色格子.不难发现 \(f(l)\ge 4,f(r)=4\) 是恒成立的,根据上面的结论,我们只需要求有多少个 \(f(l)=4\) ,即最小值的个数.
用线段树维护 \(f\) 以及最小值个数,每次 \(r\) 增加 \(1\) 时,会影响到周边的 \(4\) 个 \(2\times 2\) 的小正方形,在线段树上区间加即可.
时间复杂度 \(O(nm\log nm)\) .
CO int N=2e5+10;
struct node {int min,cnt;};
IN node operator+(CO node&a,CO node&b){
if(a.min!=b.min) return a.min<b.min?a:b;
return {a.min,a.cnt+b.cnt};
}
node tree[4*N];
int tag[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
IN void put_tag(int x,int v){
tree[x].min+=v,tag[x]+=v;
}
IN void push_down(int x){
if(tag[x]){
put_tag(lc,tag[x]),put_tag(rc,tag[x]);
tag[x]=0;
}
}
void build(int x,int l,int r){
tree[x]={0,r-l+1};
if(l==r) return;
build(lc,l,mid),build(rc,mid+1,r);
}
void modify(int x,int l,int r,int ql,int qr,int v){
if(ql<=l and r<=qr) return put_tag(x,v);
push_down(x);
if(ql<=mid) modify(lc,l,mid,ql,qr,v);
if(qr>mid) modify(rc,mid+1,r,ql,qr,v);
tree[x]=tree[lc]+tree[rc];
}
node query(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr) return tree[x];
push_down(x);
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
}
#undef lc
#undef rc
#undef mid
int mx,px[N],py[N];
vector<int> w[N];
IN int f(int x){
return x==1 or x==3;
}
void solve(vector<int> a){
int x=a[0];
a.push_back(0),sort(a.begin(),a.end());
int p=lower_bound(a.begin(),a.end(),x)-a.begin();
for(int i=1;i<=p;++i){
int t=f(p-i+1)-f(p-i);
modify(1,1,mx,a[i-1]+1,a[i],t);
}
}
int main(){
int n=read<int>(),m=read<int>();
mx=n*m;
for(int i=0;i<=n+1;++i){
w[i].resize(m+2);
w[i][0]=w[i][m+1]=mx+1;
for(int j=1;j<=m;++j){
if(i==0 or i==n+1) w[i][j]=mx+1;
else read(w[i][j]),px[w[i][j]]=i,py[w[i][j]]=j;
}
}
build(1,1,mx);
int64 ans=0;
for(int i=1;i<=mx;++i){
int x=px[i],y=py[i];
solve({w[x][y],w[x-1][y],w[x][y-1],w[x-1][y-1]});
solve({w[x][y],w[x-1][y],w[x][y+1],w[x-1][y+1]});
solve({w[x][y],w[x+1][y],w[x][y-1],w[x+1][y-1]});
solve({w[x][y],w[x+1][y],w[x][y+1],w[x+1][y+1]});
node res=query(1,1,mx,1,i);
if(res.min==4) ans+=res.cnt;
}
printf("%lld\n",ans);
return 0;
}