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)\)的最短路。需要分一分情況(可能同時滿足幾個情況):
-
\(A_1,A_2\)所在行可踏。那么分成兩種情況:
- \(A_1,A_2\)之間沒有可踏列。那么只需要找離\(A_1\)(或\(A_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\)的曼哈頓距離即可;
-
\(A_1,A_2\)所在列可踏。與情況\(1\)類似;
-
\(A_1\)所在行可踏,\(A_2\)所在列可踏。這樣可以直接把距離更新為\(A_1,A_2\)的曼哈頓距離,一種可行的方案是,先從\(A_1\)走到\(A_1\)所在行\(A_2\)所在列交點,再走到\(A_2\);
-
\(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;
}