1. 程式人生 > 實用技巧 >CDQ分治學習筆記

CDQ分治學習筆記

CDQ分治是2008 IOI金牌神仙陳丹琦在國家集訓隊引入的一種離線分治演算法,

對時間分治,時間複雜度為O(logn*單次處理複雜度)。

思路大體是這樣的:

資料結構問題的操作通常可以分為修改和查詢兩類,而每一次查詢就是詢問前面所有的修改對當前的影響,

而CDQ分治將動態的問題分解為一個個靜態的,對時間點計算影響的問題,並用分治的方法統一求解。

當前有N個操作,我們用solve(l,r)計算在[l,r]區間內的修改對區間內查詢的貢獻,做法如下:

  設mid=(l+r)/2,

  1.分治計算solve(l,mid)

  2.分治計算solve(mid+1,r)

  3.計算[l,mid]內所有的修改對[mid+1,r]的查詢的影響

solve(1,N)是呼叫入口,當l==r時,只有一項操作,可以直接返回。

我們將原來的動態問題分解成了一個個步驟3的靜態問題,其數量是(1+2+4+...+2^k)=O(N)個,其中2^k<=N。

而每一個原來的詢問由O(logN)個靜態問題組成,由於總共遞迴O(logN)層,所以複雜度是O(logn*單次處理複雜度)。

因為每一個靜態問題的時復只與當前的l,r有關,因此效率較高。

例題 天使的玩偶https://www.luogu.com.cn/problem/P4169

對於每個查詢,要計算min{|x-xi|+|y-yi|},為了去掉絕對值符號,我們將詢問分成四瓣,分別查詢當前點左下、左上、右下、右上的最近距離。

將其分解成

  x+y-max{xi+yi}

  x-y-max{xi-yi}

  -x+y-max{-xi+yi}

  -x-y-max{-xi-yi}

在計算步驟3時,列舉四個方向,並用樹狀陣列維護最大值即可,

實現細節看程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAX 300010
#define Inf 1000001
#define lowbit(x) x&-x
#define PII pair<int,int>
#define
mk make_pair #define ft first #define sc second using namespace std; struct pos{ int x,y; int type,ans; }p[MAX*2]; inline int read(){ int s=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar(); return s*w; } int t[Inf],cnt,q[MAX*2]; PII opt[MAX*2]; void update(int x,int k){ for(;x<=Inf&&t[x]<k;x+=lowbit(x))t[x]=k; } int get(int x){ int res=0x80808080; for(;x;x-=lowbit(x))res=max(res,t[x]); return res; } void del(int x){ for(;x<=Inf;x+=lowbit(x))t[x]=0x80808080; } int type(int x,int y,int id){ switch(id){ case 0:return x+y; case 1:return x-y; case 2:return -x+y; case 3:return -x-y; } } bool cmp(int a,int b){return p[a].x<p[b].x;} void work(int l,int r){ int mid=(l+r)/2,x,y; int num; cnt=0; for(int i=l;i<=mid;i++) if(!p[i].type)q[cnt++]=i; for(int i=mid+1;i<=r;i++) if(p[i].type)q[cnt++]=i; sort(q,q+cnt,cmp); for(int id=0;id<4;id++){ num=-1; for(int i=(id<2?0:cnt-1);(id<2?i<cnt:i>=0);i+=(id<2?1:-1)){ x=p[q[i]].x,y=p[q[i]].y; if(!p[q[i]].type){ opt[++num]=mk(id&1?Inf-y:y,type(x,y,id)); update(opt[num].ft,opt[num].sc); } else{ if(num==-1)continue; p[q[i]].ans=min(p[q[i]].ans,type(x,y,id)-get(id&1?Inf-y:y)); } } for(int i=0;i<=num;i++){ del(opt[i].ft); } } } void cdq(int left,int right){ if(left==right)return; int mid=(left+right)/2; cdq(left,mid); cdq(mid+1,right); work(left,right); } int main(){ int n,m; cin>>n>>m; for(int i=0;i<n+m;i++){ if(i>=n)p[i].type=read()-1; else p[i].type=0; p[i].x=read(),p[i].y=read(); p[i].ans=2*Inf; } memset(t,0x80,sizeof(t)); cdq(0,n+m-1); for(int i=0;i<n+m;i++){ if(p[i].type)printf("%d\n",p[i].ans); } return 0; }

其中0x80808080是極小值