1. 程式人生 > 實用技巧 >LOJ6698 一鍵挖礦

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;
}