LOJ 3188 - 無人駕駛出租車


yjz對這道題的評價是:

“這是一道很基礎的題。”

LOJ題目頁面傳送門

題意見LOJ。

剛看完題以為又是個甚么實時維護最短路的神仙科技。其實所謂的神仙科技都是不存在的。

注意到,任意時刻,對於任意最大踏雪深度,可踏的區域一定是一些行和一些列的並。這樣把可踏區域給提出來就好辦多了。

於是我們維護每行每列的最后一次被清空的時間戳(如果沒有被清空過就是\(0\),因為一開始是空的)\(x\),設當前時間戳為\(i\),那么這行/列都可踏當且僅當\(i-x\leq k_i\Leftrightarrow x\geq i-k_i\)

考慮如何求\(A_1(r_1,c_1)\)\(A_2(r_2,c_2)\)的最短路。需要分一分情況(可能同時滿足幾個情況):

  1. \(A_1,A_2\)所在行可踏。那么分成兩種情況:

    1. \(A_1,A_2\)之間沒有可踏列。那么只需要找離\(A_1\)(或\(A_2\))最近的兩側的可踏列,算一下距離更新答案即可;
    2. \(A_1,A_2\)之間有可踏列。那么顯然直接把距離更新為\(A_1,A_2\)的曼哈頓距離即可(這達到了下限)。

    為了方便寫,我們不論是情況\(1.1\)還是情況\(1.2\)都按照情況\(1.1\)的方法來處理,這樣不用分類,而且正確性不變。

    需要注意的是,可能有一些列,它並不是可踏列,但是在\(A_1\)\(A_2\)的那一段截下來每處所在行都是可踏行,這樣也是可以走的。這個異常好處理,注意到如果有這樣的情況,那么在\(A_1\)\(A_2\)段的任意列都可以走,那么直接把距離更新為\(A_1,A_2\)的曼哈頓距離即可;

  2. \(A_1,A_2\)所在列可踏。與情況\(1\)類似;

  3. \(A_1\)所在行可踏,\(A_2\)所在列可踏。這樣可以直接把距離更新為\(A_1,A_2\)的曼哈頓距離,一種可行的方案是,先從\(A_1\)走到\(A_1\)所在行\(A_2\)所在列交點,再走到\(A_2\)

  4. \(A_1\)所在列可踏,\(A_2\)所在行可踏。與情況\(3\)類似。

那么唯一的難點就在於怎么求“離\(A_1\)(或\(A_2\))最近的兩側的可踏列”。容易發現這其實就是求區間中\(c_1\)的前驅后繼(區間基於時間戳,是\([i-k_i,i)\),若某時間有清空列的操作,則此處有值為清空的列)。zszz,線段樹套平衡樹求區間前驅后繼是可以做到\(\mathrm O\!\left(\log^2n\right)\)(假設\(n,m\)同階)的,我們也可以二分+可持久化平衡樹(雖然我還不會)求區間排名做到\(\mathrm O\!\left(\log^2n\right)\)。不過通過看題解可知是有1log方法的。

考慮交換定義域和值域(老套路了),基於列的編號維護最后一次被清空的時間戳。這樣我們可以維護一棵線段樹,單點修改,至於求前驅后繼,以前驅為例,設要求\(x\)的前驅,我們從右往左把區間\([1,x]\)剖成線段樹上的若干區間,對第一個剖出來的(即最右邊的)有可踏列的(判斷這個只需要維護時間戳的\(\max\))區間線段樹二分即可。哦對之前忘了說了,“在\(A_1\)\(A_2\)的那一段截下來每處所在行都是可踏行”這個東西也要維護,現在正好有基於列編號的線段樹了,順帶維護一下區間\(\min\)即可。

至於為啥交換定義域和值域就可以少一個\(\log\)?這是一個哲學問題。其實很簡單,因為只有要求的東西在定義域上,我們才可以使勁往左/右靠,從而實現將二分與DS操作合體。

時間復雜度\(\mathrm O(q\log n)\)(常數異常大別嫌棄)

代碼:

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=1000000;
int n,m,qu; 
struct segtree{//基於行/列編號的線段樹 
	struct node{int l,r,mx,mn;}nd[N<<2];
	#define l(p) nd[p].l
	#define r(p) nd[p].r
	#define mx(p) nd[p].mx
	#define mn(p) nd[p].mn
	void bld(int l,int r,int p=1){
		l(p)=l;r(p)=r;mx(p)=mn(p)=0;//一開始都是0 
		if(l==r)return;
		int mid=l+r>>1;
		bld(l,mid,p<<1);bld(mid+1,r,p<<1|1);
	}
	void init(int r){bld(1,r);}
	void sprup(int p){mx(p)=max(mx(p<<1),mx(p<<1|1));mn(p)=min(mn(p<<1),mn(p<<1|1));}
	void chg(int x,int v,int p=1){//單點修改 
		if(l(p)==r(p))return mx(p)=mn(p)=v,void();
		int mid=l(p)+r(p)>>1;
		chg(x,v,p<<1|(x>mid));
		sprup(p);
	}
	int at(int x){//單點查詢 
		int p=1;
		while(l(p)<r(p)){
			int mid=l(p)+r(p)>>1;
			p=p<<1|(x>mid);
		}
		return mx(p);
	}
	int _mn(int l,int r,int p=1){//區間最小值 
		if(l<=l(p)&&r>=r(p))return mn(p);
		int mid=l(p)+r(p)>>1,res=inf;
		if(l<=mid)res=min(res,_mn(l,r,p<<1));
		if(r>mid)res=min(res,_mn(l,r,p<<1|1));
		return res;
	}
	int prv(int l,int r,int lim,int p=1){//前驅 
		if(l<=l(p)&&r>=r(p))
			if(mx(p)<lim)return 0;//沒有的話滾蛋 
			else{//有的話線段樹二分 
				while(l(p)<r(p)){
					if(mx(p<<1|1)>=lim)p=p<<1|1;
					else p=p<<1;
				}
				return l(p);
			}
		int mid=l(p)+r(p)>>1;
		if(r>mid){//從右往左 
			int res=prv(l,r,lim,p<<1|1);
			if(res)return res;
		}
		if(l<=mid){
			int res=prv(l,r,lim,p<<1);
			if(res)return res;
		}
		return 0;
	}
	int nxt(int l,int r,int lim,int p=1){//后繼,同上 
		if(l<=l(p)&&r>=r(p))
			if(mx(p)<lim)return 0;
			else{
				while(l(p)<r(p)){
					if(mx(p<<1)>=lim)p=p<<1;
					else p=p<<1|1;
				}
				return l(p);
			}
		int mid=l(p)+r(p)>>1;
		if(l<=mid){
			int res=nxt(l,r,lim,p<<1);
			if(res)return res;
		}
		if(r>mid){
			int res=nxt(l,r,lim,p<<1|1);
			if(res)return res;
		}
		return 0;
	}
}segt_r/*行*/,segt_c/*列*/;
int main(){
	cin>>n>>m>>qu;
	segt_r.init(n);segt_c.init(m);//初始化 
	for(int i=1;i<=qu;i++){
		int tp,a,b,c,d,e;
		scanf("%d%d",&tp,&a);
		if(tp==1)segt_r.chg(a,i);//單點修改 
		else if(tp==2)segt_c.chg(a,i);//單點修改 
		else{
			scanf("%d%d%d%d",&b,&c,&d,&e);
			int ans=inf;
			int r_a=segt_r.at(a),r_c=segt_r.at(c),c_b=segt_c.at(b),c_d=segt_c.at(d);
			if(r_a>=i-e&&c_d>=i-e||c_b>=i-e&&r_c>=i-e){printf("%d\n",abs(a-c)+abs(b-d));continue;}//情況3,4 
			if(c_b>=i-e&&c_d>=i-e){//情況2 
				if(segt_c._mn(min(b,d),max(b,d))>=i-e){printf("%d\n",abs(a-c)+abs(b-d));continue;}//需要注意的是 
				int prv=segt_r.prv(1,a,i-e),nxt=segt_r.nxt(a,n,i-e);
				if(prv)ans=min(ans,abs(prv-a)+abs(prv-c)+abs(b-d));//前驅更新答案 
				if(nxt)ans=min(ans,abs(nxt-a)+abs(nxt-c)+abs(b-d));//后繼更新答案 
			}
			if(r_a>=i-e&&r_c>=i-e){//情況1 
				if(segt_r._mn(min(a,c),max(a,c))>=i-e){printf("%d\n",abs(a-c)+abs(b-d));continue;}//需要注意的是 
				int prv=segt_c.prv(1,b,i-e),nxt=segt_c.nxt(b,m,i-e);
				if(prv)ans=min(ans,abs(prv-b)+abs(prv-d)+abs(a-c));//前驅更新答案 
				if(nxt)ans=min(ans,abs(nxt-b)+abs(nxt-d)+abs(a-c));//后繼更新答案 
			}
			printf("%d\n",ans==inf?-1:ans);
		}
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM