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]\)
你發現 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;
}