1. 程式人生 > 實用技巧 >《演算法競賽進階指南》0x47離線分治演算法 CDQ分治

《演算法競賽進階指南》0x47離線分治演算法 CDQ分治

題目連結:https://www.acwing.com/problem/content/256/

題目給出n個初始點,m個操作,可能是加上一個新的點,也可能是給出一個點,查詢離他曼哈頓距離最近的點的距離。由於是動態問題,有查詢與改動是無序的,所以考慮使用基於時間的離線分治演算法CDQ分治。該演算法在遞迴計算左右的情況之後只需要計算左半邊的更新對右半邊的查詢的影響。故變成了一個靜態的問題,這時候將曼哈頓距離展開成四個查詢即可,由於狀態控制有兩個,通過排序解決一個狀態控制,另一個可以通過樹狀陣列查詢字首最大值解決。樹狀陣列每次進行calc之後都要回到原狀態,不能每次都開一個樹狀陣列。(實際上AcWing就過了17/20的資料,人沒了)

程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1000010;
const int inf=(1<<30);
struct rec{int x,y,z;};
rec a[maxn];//操作序列
rec b[maxn];//靜態問題的座標,及其下標
int c[maxn],tot;//樹狀陣列
int ans[maxn],n,m,t;
inline bool operator
< (const rec& a,const rec& b){ return a.x<b.x || (a.x==b.x && a.y<b.y); } inline int read(){ int ans=0,w=1; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();} while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar(); return
ans*w; } //查詢字首最大值 inline int ask(int x){ int ans=-inf; while(x){ ans=max(ans,c[x]); x-=x&(-x); } return ans; } //維護字首最大值 inline void update(int x,int y){ while(x<tot){ c[x]=max(c[x],y); x+=x&(-x); } } inline void calc(int st,int ed,int de,int dx,int dy){ for(int i=st;i!=ed;i+=de){ int y=(dy==1)?b[i].y:tot-b[i].y;//查詢的是字首 int tmp=dx*b[i].x+dy*b[i].y; if(a[b[i].z].z==1)update(y,tmp); else ans[b[i].z]=min(ans[b[i].z],tmp-ask(y)); } for(int i=st;i!=ed;i+=de){ int y=(dy==1)?b[i].y:tot-b[i].y; if(a[b[i].z].z==1){//如果是一個更新就將樹狀陣列更新回原狀態 for(int j=y;j<tot;j+=j&(-j))c[j]=-inf; } } } inline void cdqdiv(int l,int r){ int mid=(l+r)>>1; if(l<mid)cdqdiv(l,mid); if(mid+1<r)cdqdiv(mid+1,r); t=0;//前半段的修改數量+後半段的詢問數量 for(int i=l;i<=r;i++){ if(i<=mid && a[i].z==1 || i>mid && a[i].z==2) b[++t]=a[i],b[t].z=i;//儲存下標,用來更新ans } sort(b+1,b+t+1); calc(1,t+1,1,1,1);//計算[l,mid]段的修改對[mid+1,r]段的查詢的貢獻 calc(1,t+1,1,1,-1); calc(t,0,-1,-1,-1); calc(t,0,-1,-1,1); } int main(){ cin>>n>>m; m+=n; //把初始的點也認為是一種加點的操作 //由於樹狀陣列中下標是從1開始的,所以y++ for(int i=1;i<=n;i++) a[i].x=read(),a[i].y=read(),a[i].y++,a[i].z=1; for(int i=n+1;i<=m;i++) a[i].z=read(),a[i].x=read(),a[i].y=read(),a[i].y++; for(int i=1;i<=m;i++)tot=max(tot,a[i].y);//樹狀陣列下標的最大值 tot++; memset(ans,0x3f,sizeof(ans)); memset(c,0xcf,sizeof(c));//賦值,一個絕對值極大的負數 cdqdiv(1,m); for(int i=1;i<=m;i++) if(a[i].z==2)printf("%d\n",ans[i]); }