Luogu P4169 [Violet]天使玩偶/SJY擺棋子
傳送門
二維平面修改+查詢,cdq分治可以解決。
求關於某個點曼哈頓距離(x,y坐標)最近的點——dis(A,B) = |Ax-Bx|+|Ay-By|
但是如何去掉絕對值呢?
查看題解發現假設所有的點都在查詢點的左下方,dis(A,B) = (Ax-Bx)+(Ay-By) = (Ax+Ay)-(Bx+By)
只要求滿足Bx<Ax,By<Ay且Bx,By之和最大的點就好了。
那麽如何把所有的點轉化到該查詢的左下呢?
對於每個查詢,可以把一、二、四象限的點都通過對稱轉移到第三象限。但查詢很多,不可能一個個翻轉。
換個思路,如果把整個平面翻轉三次,進行四次cdq分治,每次都只考慮左下的點,所有的點就都遍歷到了!
記錄最大的x或y值為邊界len,每次沿len翻轉。例如沿y軸翻轉時,x = len-x
那麽每個操作有三維——時間、x坐標、y坐標
時間在輸入時已經排好了;x歸並排序;y仿照陌上花開,用樹狀數組記錄。
優化 & 註意
這道題坑點超級多...而且四次cdq分治會得到一個感人的復雜度,所以必須考慮優化,卡一卡常數(我選擇吸氧)
- cdq內的歸並排序代替每次sort。
- 因為每次cdq完順序會被打亂,如果重新按時間O(nlogn)排序,不如每次存入一個臨時數組,然後O(n)直接復制過去。
但是ans需要存入初始的數組中,所以結構體需要一個.id來記錄打亂前的時間,也就是原數組下標。賦值應該寫a[b[t2].id].ans,
而不是a[t2].ans。
並且,由於每次查詢點的x,y也會更改,所以ans裏不能直接存max(Bx+By),而應該為min((Ax+Ay)-(Bx+By))。
- 如果某個點在坐標軸上,那麽它的x或y為0。存入樹狀數組時,會因為lowbit()==0而陷入死循環。所以存入時,將x,y分別+1。
同樣的,如果某個點在翻轉邊界len上,翻轉時也會變為0。所以len也要++。
- 考慮這樣一種情況:某一點非常靠近邊界,導致某次翻轉時,沒有點在它的左下。這樣查詢時默認返回了0。
當前的“原點”比實際上的點離該查詢點更近,這樣最終的距離就成了這個點到原點的距離,但原點是不存在的(經過剛剛的更改,已經沒有x或y坐標為0的點)
為避免這種情況,當查詢時需要特判,若為0則返回-INF。
- 由於初始值——前n個點一定是修改操作,可以把它們直接排好序,不用遞歸檢驗是否有查詢。(不過我覺得有點麻煩就沒寫)
這道題的代碼不難,但是細節特別多,很難debug...寫的時候思路一定要清晰了!
代碼如下
// luogu-judger-enable-o2 #include<cstdio> #include<iostream> #define MogeKo qwq using namespace std; const int maxn = 1e7+10; const int INF = 2e7+10; int n,q,opt,x,y,len; struct node { int x,y,type,id,ans; } a[maxn],b[maxn],tem[maxn]; struct BIT { int m[maxn]; int lowbit(int x) { return x & -x; } void update(int x,int v) { for(; x <= len; x+= lowbit(x)) m[x] = max(m[x],v); } int query(int x) { int ans = 0; for(; x; x-=lowbit(x)) ans = max(ans,m[x]); return ans?ans:-INF; } void clear(int x) { for(; m[x]; x+= lowbit(x)) m[x] = 0; } } tree; void cdq(int L,int R) { if(L == R) return; int mid = L+R >> 1; cdq(L,mid),cdq(mid+1,R); int t1 = L,t2 = mid+1; int k = L; while(t2 <= R) { while(t1 <= mid && b[t1].x <= b[t2].x) { if(b[t1].type == 1) tree.update(b[t1].y, b[t1].x+b[t1].y); tem[k++] = b[t1++]; } if(b[t2].type == 2) a[b[t2].id].ans = min(a[b[t2].id].ans,b[t2].x+b[t2].y-tree.query(b[t2].y)); tem[k++] = b[t2++]; } for(int i = L; i <= t1-1; i++) if(b[i].type == 1) tree.clear(b[i].y); while(t1 <= mid) tem[k++] = b[t1++]; for(int i = L;i <= R;i++) b[i] = tem[i]; } void solve(int rx,int ry) { for(int i = 1; i <= n+q; i++) { b[i] = a[i]; if(rx) b[i].x = len - b[i].x; if(ry) b[i].y = len - b[i].y; } cdq(1,n+q); } int main() { scanf("%d%d",&n,&q); for(int i = 1; i <= n; i++) { scanf("%d%d",&x,&y); a[i].type = 1; a[i].id = i; a[i].x = ++x; a[i].y = ++y; len = max(len,max(x,y)); } for(int i = n+1; i <= n+q; i++) { scanf("%d%d%d",&opt,&x,&y); a[i].type = opt; a[i].id = i; a[i].x = ++x; a[i].y = ++y; a[i].ans = INF; len = max(len,max(x,y)); } len++; solve(0,0),solve(0,1),solve(1,0),solve(1,1); for(int i = n+1; i <= n+q; i++) if(a[i].type == 2) printf("%d\n",a[i].ans); return 0; }View Code
Luogu P4169 [Violet]天使玩偶/SJY擺棋子