KD樹小結

很久之前我就想過怎么快速在二維平面上查找一個區域的信息,思考許久無果,只能想到幾種優秀一點的暴力。

KD樹就是干上面那件事的。

別的不多說,趕緊把自己的理解寫下來,免得涼了。

?

KD樹的組成

以維護k維空間(x,y,……)內的KD樹為例,主要由一下三部分組成:

  1. p[k],代表樹上這個結點所儲存的點(在題目中給出的/你自己加上的點集中的一個點)。
  2. ch[2],表示它的子結點(沒錯,KD樹是一棵二叉樹)
  3. mi[k]與mx[k],mi/mx[i]代表KD樹這個結點統轄的所有點的第i-1范圍。比如說mi[1]=2,mx[1]=4,就代表這棵樹統轄的點的y坐標都在[2,4]內。

?

不看mi和mx,長得就和splay/trie樹一樣,一個p維護當前節點,一個ch[2]記錄左右兒子。

不看p[k],長得就和線段樹一樣,有左右兒子和區間信息。

沒錯,KD樹功能如線段樹,結點維護區域信息;形態如splay/trie樹,每個結點有實際的值和意義。

?

KD樹的構建

一般題目都是二維平面。下面就以二維平面KD樹的構建為例。

讀入把點存進結構體數組a中,坐標分別為a[x].p[i]。

inline void build(int &x,int l,int r,int type){x=(l+r)>>1;now=type;nth_element(a+l,a+x,a+r+1,cmp);nd=a[x];newnode(x);if(l<x)build(ch[x][0],l,x-1,type^1);else ch[x][0]=0;if(x<r)build(ch[x][1],x+1,r,type^1);else ch[x][1]=0;pushup(x);
}build(kd.root,1,n,0);

非常優美……對type、now作用不明的同學請繼續閱讀……你要現在就明白就奇怪了

系統函數nth_element(a+l,a+x,a+r+1),頭文件algorithm,需定義<或cmp函數。

作用:把排序后第x大的放到第x位,比它小的放進左邊,比它大的放進右邊(兩邊無序)。

注意區間開閉:左閉右開,中間也是閉合的。

復雜度:平均,期望是O(n)?可以接受。

下面給出cmp、newnode、pushup代碼。

struct Node{int p[2],mi[2],mx[2];}a[N];
inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];}
inline void Min(int &x,int y){x=x<y?x:y;}
inline void Max(int &x,int y){x=x>y?x:y;}
inline void pushup(int x){int ls=ch[x][0],rs=ch[x][1];if(ls){Min(T[x].mi[0],T[ls].mi[0]);Max(T[x].mx[0],T[ls].mx[0]);Min(T[x].mi[1],T[ls].mi[1]);Max(T[x].mx[1],T[ls].mx[1]);}if(rs){Min(T[x].mi[0],T[rs].mi[0]);Max(T[x].mx[0],T[rs].mx[0]);Min(T[x].mi[1],T[rs].mi[1]);Max(T[x].mx[1],T[rs].mx[1]);}
}inline void newnode(int x){T[x].p[0]=T[x].mi[0]=T[x].mx[0]=nd.p[0];T[x].p[1]=T[x].mi[1]=T[x].mx[1]=nd.p[1];
}

不要問我為什么辣么長,為了減常沖榜,把循環展開了……

聰明的讀者已經發現KD樹的構建巧妙之處。它不是純粹按照x維,或者某一維排序,而是先按x維,再按y維,再按z維,再……最后又回到x維……

這樣分割的區域更加整齊劃一更加均勻緊縮,不像上面的按照某一維劃分,到最后變成一條條長條,KD樹劃分到底的圖案還是很好看的。

這樣分割有什么好處呢?等你真正領悟了KD樹的精髓之后你就會發現……嘿嘿嘿……

(就是為了把這個暴力數據結構剪枝剪更多跑更快)

?

KD樹的操作

1.往KD樹上插點

插點可以分為插新點和插老點。如果有老點,特判一句,把信息覆蓋即可。

inline void insert(int &x,int type){if(!x){x=++cnt,newnode(cnt);return;}if(nd.p[0]==T[x].p[0] && nd.p[1]==T[x].p[1]){……(自行維護);return;}if(nd.p[type]<T[x].p[type])insert(ch[x][0],type^1);else insert(ch[x][1],type^1);pushup(x);
}

依然非常的美妙……等等有什么不對?

我們能估計出一棵剛建好的KD樹深度是O(log)的。

但你這么隨便亂插……有道題叫HNOI2017 spaly 插入不旋轉的單旋spaly見過?T成茍。

這都不是問題!知不知道有一種數據結構叫做替罪羊樹哇?

知道替罪羊樹怎么保證復雜度的嗎?

重構!大力重構!自信重構!不爽就重構!

為了省事大概每插入10000次就重構一次好了……

if(kd.cnt==sz){for(int i=1;i<=sz;++i)a[i]=kd.T[i];kd.rebuild(kd.root,1,sz,0);sz+=10000;
}

?

2.在KD樹上查詢

  • 如果是單點(給定點)查詢:
    • 太簡單啦!把插入改改就闊以辣!
  • 如果是查詢距離一個點(x',y')最近的點(曼哈頓距離,|x-x'|+|y-y'|):
    • 首先我們看暴力的剪枝:按某一維排序,如果該維的差過大就不管了。
    • 而令我們期待的KD樹呢?呃不好意思,它也是這么做的……
    • 我們維護過兩個叫做mi[]和mx[]的東西吧……這個時候就是它派上用場了。
    • 具體還請看代碼吧:
      //查詢的點(x',y')儲存在nd中。
      //這里的l,r就是mi,mx的意思。
      inline int dis(Node p,int x,int ans=0){for(int i=0;i<2;++i)ans+=max(0,t[x].l[i]-p.p[i])+max(0,p.p[i]-t[x].r[i]);return ans;
      }inline void query(int x){Ans=min(Ans,abs(t[x].p[0]-nd.p[0])+abs(t[x].p[1]-nd.p[1]));int dl=ch[x][0]?dis(nd,ch[x][0]):Inf;int dr=ch[x][1]?dis(nd,ch[x][1]):Inf;if(dl<dr){if(dl<Ans)query(ch[x][0]);if(dr<Ans)query(ch[x][1]);}else{if(dr<Ans)query(ch[x][1]);if(dl<Ans)query(ch[x][0]);}
      }
    • dis():如果當前點在這個區間內就是0,否則就是最極的點到它的距離。
    • 聰明絕頂的你已經發現了……這TM就是個暴力。
    • 其實這個數據結構就是一個暴力……
    • 當暴力有了時間復雜度證明……還叫暴力么?讀書人的事,能叫偷么?
    • 這么暴力有幾個好處:不用枚舉所有點;剪枝有效及時。
    • 復雜度有保障,大概在O(√n)級別下,主要看數據。
  • 如果是區間查詢,以區間查詢點權和為例(之前就有維護好):
    • inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;}
      inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;}inline int query(int x,int x1,int y1,int x2,int y2){int ans=0;if(!x)return ans;if(in(x1,x2,T[x].mi[0],T[x].mx[0]))if(in(y1,y2,T[x].mi[1],T[x].mx[1]))return T[x].sum;if(out(x1,x2,T[x].mi[0],T[x].mx[0]))return 0;if(out(y1,y2,T[x].mi[1],T[x].mx[1]))return 0;if(in(x1,x2,T[x].p[0],T[x].p[0]))if(in(y1,y2,T[x].p[1],T[x].p[1]))ans+=T[x].val;return ans+query(ch[x][0],x1,y1,x2,y2)+query(ch[x][1],x1,y1,x2,y2);
      }
    • 別看代碼長又看起來復雜,寫起來跟線段樹似的,還是一樣的暴力搞。

?

KD樹的基本姿勢大概就是這個樣子……好寫不好寫錯,基本上都是個板子。

附上學長的一(三)句話,從各方面進行了深度總結:

能不寫最好還是不要寫吧,輕松被卡→_→,也許可以出奇制勝?如果要寫,重新構樹是個不錯的選擇。發現大數據跑不過,多半是剪枝掛了。

還是給個鏈接……MashiroSky大爺。

upd:以當前坐標差最大的來做type應該比輪換type更優秀……

例題有"SJY擺棋子"、"簡單題"等,在此就不做贅述了。

比較有意思的應用就是【bzoj3489】 A simple rmq problem,正如上面所言,KD樹解決傳統數據結構題出奇制勝。

附上"BZOJ4066簡單題"代碼一份,操作是單點修改+矩形求和在線。

?

#include    <iostream>
#include    <cstdio>
#include    <cstdlib>
#include    <algorithm>
#include    <vector>
#include    <cstring>
#include    <queue>
#include    <complex>
#include    <stack>
#define LL long long int
#define dob double
#define FILE "bzoj_4066"
//#define FILE "簡單題"
using namespace std;const int N = 200010;
int n,lst,now,sz=10000;inline int gi(){int x=0,res=1;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();return x*res;
}inline void Min(int &x,int y){x=x<y?x:y;}
inline void Max(int &x,int y){x=x>y?x:y;}
struct Node{int p[2],mi[2],mx[2],val,sum;}a[N];
inline bool cmp(const Node &a,const Node &b){return a.p[now]<b.p[now];}
struct KD_Tree{int ch[N][2],root,cnt;Node T[N],nd;inline void pushup(int x){int ls=ch[x][0],rs=ch[x][1];if(ls){Min(T[x].mi[0],T[ls].mi[0]);Max(T[x].mx[0],T[ls].mx[0]);Min(T[x].mi[1],T[ls].mi[1]);Max(T[x].mx[1],T[ls].mx[1]);}if(rs){Min(T[x].mi[0],T[rs].mi[0]);Max(T[x].mx[0],T[rs].mx[0]);Min(T[x].mi[1],T[rs].mi[1]);Max(T[x].mx[1],T[rs].mx[1]);}T[x].sum=T[x].val;if(ls)T[x].sum+=T[ls].sum;if(rs)T[x].sum+=T[rs].sum;}inline void newnode(int x){T[x].p[0]=T[x].mi[0]=T[x].mx[0]=nd.p[0];T[x].p[1]=T[x].mi[1]=T[x].mx[1]=nd.p[1];T[x].sum=T[x].val=nd.val;}inline void insert(int &x,int type){if(!x){x=++cnt,newnode(cnt);return;}if(nd.p[0]==T[x].p[0] && nd.p[1]==T[x].p[1]){T[x].val+=nd.val;T[x].sum+=nd.val;return;}if(nd.p[type]<T[x].p[type])insert(ch[x][0],type^1);else insert(ch[x][1],type^1);pushup(x);}inline void rebuild(int &x,int l,int r,int type){x=(l+r)>>1;now=type;nth_element(a+l,a+x,a+r+1,cmp);nd=a[x];newnode(x);if(l<x)rebuild(ch[x][0],l,x-1,type^1);else ch[x][0]=0;if(x<r)rebuild(ch[x][1],x+1,r,type^1);else ch[x][1]=0;pushup(x);}inline bool in(int l,int r,int xl,int xr){return l<=xl && xr<=r;}inline bool out(int l,int r,int xl,int xr){return xr<l || r<xl;}inline int query(int x,int x1,int y1,int x2,int y2){int ans=0;if(!x)return ans;if(in(x1,x2,T[x].mi[0],T[x].mx[0]))if(in(y1,y2,T[x].mi[1],T[x].mx[1]))return T[x].sum;if(out(x1,x2,T[x].mi[0],T[x].mx[0]))return 0;if(out(y1,y2,T[x].mi[1],T[x].mx[1]))return 0;if(in(x1,x2,T[x].p[0],T[x].p[0]))if(in(y1,y2,T[x].p[1],T[x].p[1]))ans+=T[x].val;return ans+query(ch[x][0],x1,y1,x2,y2)+query(ch[x][1],x1,y1,x2,y2);}}kd;int main()
{freopen(FILE".in","r",stdin);freopen(FILE".out","w",stdout);n=gi();while(1){int type=gi();if(type==3)break;int x1=gi()^lst,y1=gi()^lst;if(type==1){int A=gi()^lst;kd.nd.p[0]=x1;kd.nd.p[1]=y1;kd.nd.sum=kd.nd.val=A;kd.insert(kd.root,0);if(kd.cnt==sz){for(int i=1;i<=sz;++i)a[i]=kd.T[i];kd.rebuild(kd.root,1,sz,0);sz+=10000;}}if(type==2){int x2=gi()^lst,y2=gi()^lst;lst=kd.query(kd.root,x1,y1,x2,y2);printf("%d\n",lst);}}fclose(stdin);fclose(stdout);return 0;
}
簡單題

?

?

?

轉載于:https://www.cnblogs.com/fenghaoran/p/8176236.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/453763.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/453763.shtml
英文地址,請注明出處:http://en.pswp.cn/news/453763.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

多元函數求極值中的a_多元函數的條件極值和拉格朗日乘數法

、條件極值、拉格朗日乘數法1. 轉化為無條件極值在討論多元函數極值問題時&#xff0c;如果遇到除了在定義域中尋求駐點(可能的極值點)外&#xff0c;對自變量再無別的限制條件&#xff0c;我們稱這類問題為函數的無條件極值。如求的極值&#xff0c;就是無條件極值問題。然而在…

深度學習之 RPN(RegionProposal Network)- 區域候選網絡

anchor boxes基本概念與作用: feature map 上的一個點可以映射回輸入圖片上的一個點&#xff0c;以特征圖上這個點為中心&#xff0c;預先人為設定 k 個 boxes&#xff0c;這些 boxes 就稱為在這個點上生成的 k 個 anchor boxes&#xff08;所有anchor boxes的中心點坐標是一樣…

h264的碼率控制 JVT-G012

開始看h264的碼率控制&#xff0c;很多地方都提到 G012&#xff0c;拿來做為參考比較&#xff0c;看來很有必要研究清楚。 偶這人&#xff0c;E文文檔不翻譯的話&#xff0c;看過就忘了&#xff0c;于是草草翻譯了下&#xff0c;因為不打算做B幀&#xff0c;也不準備在同一幀中…

Android RecyclerView嵌套EditView實時更新Item數據

一、場景&#xff08;例如&#xff1a;購物車&#xff09; 1、當我們需要以列表樣式管理某些數據時&#xff0c;可能需要列表項的某個字段可編輯 2、編輯Item上的某個字段后可能還要更新相關字段的值 二、可能遇到的問題 1、列表滑動導致輸入框中的數據錯位&#xff08;或者焦點…

workbench拓撲優化教程_優化技術在水泵水力設計的應用(上篇)

文章來源&#xff1a;安世亞太官方訂閱號&#xff08;搜索&#xff1a;Peraglobal&#xff09;CFD技術在泵的內流數值模擬、研究泵內部流動規律和結構方面已廣泛應用&#xff0c;取得了很多成果。但是初步設計的產品如果通過CFD仿真得到的性能曲線不能滿足使用要求&#xff0c;…

深度學習之 TensorRT

1 簡介 TensorRT是一個高性能的深度學習推理&#xff08;Inference&#xff09;優化器&#xff0c;可以為深度學習應用提供低延遲、高吞吐率的部署推理。TensorRT可用于對超大規模數據中心、嵌入式平臺或自動駕駛平臺進行推理加速。TensorRT現已能支持TensorFlow、Caffe、Mxne…

H.264筆記

H.264標準寫得比較繁復&#xff0c;所以考慮在瀏覽完Whitepaper之后就開始研讀X264代碼。X264代碼風格還是比較清晰簡潔的。根據對標準得理解&#xff0c;Picture Order Count在Slice解碼的一開始就被提及&#xff1a;I0 B1 B2 P3 B4 B5 P6I0 P3 B1 B2 P6 B4 B5于是I0的POC是0&…

進制轉換中dbho是什么意思_什么是網段?二進制十進制如何互相轉換?看完這篇,你就全明白了...

之前的文章講了ip&#xff0c;子網掩碼&#xff0c;網關的關系&#xff0c;今天著重講一下網段。我們用傻瓜交換機通訊時&#xff0c;一個網段的設備才能互相通訊&#xff0c;怎么能判斷兩個ip是同一個網段呢&#xff1f;今天就簡單的說一下。(這篇文章用語音聽可以起到催眠作用…

【網絡流24題】星際轉移問題(最大流)

【網絡流24題】星際轉移問題&#xff08;最大流&#xff09; 題面 Cogs 題解 因為天數是未知的&#xff0c;所以我們要想辦法處理天數 可以選擇二分或者依次累加天數 因為數據范圍較小&#xff0c;使用二分可能反而復雜度會增高 所以使用不斷累加天數 那么&#xff0c;把所有的…

使用 gunicorn 部署flask項目

1、WSGI協議 Web框架致力于如何生成HTML代碼&#xff0c;而Web服務器用于處理和響應HTTP請求。Web框架和Web服務器之間的通信&#xff0c;需要一套雙方都遵守的接口協議。WSGI協議就是用來統一這兩者的接口的。 2、WSGI容器 常用的WSGI容器有Gunicorn和uWSGI&#xff0c;但G…

軟件需求與問題解決

&#xff08;一&#xff09; 小滿當上項目經理后不久&#xff0c;參與了一個大項目。當時市場簽下來的時候&#xff0c;公司里面是歡天喜地的。項目做了一年多。到了交付的時候&#xff0c;用戶卻很不滿意&#xff0c;當初說好的東西&#xff0c;好多都變了卦。用戶是上帝&…

flex 換主軸后子元素占滿_Chrome72 嵌套 flex 布局修改,你的網站可能會發生布局錯亂...

起源2019 年 1 月 29 日&#xff0c;Chrome72 正式版(72.0.3626.81)發布&#xff0c;本次發布帶來了一個改變&#xff0c;且沒有在更新日志中提及&#xff0c;該改變導致某些網站發生了布局錯亂。該改變主要針對的是嵌套的flex布局&#xff0c;下面我們一起看下是怎么回事。問題…

使用 Django + Wusgi + Nginx 部署 Django

如何在生產上部署Django? Django的部署可以有很多方式&#xff0c;采用 nginxuwsgi 的方式是其中比較常見的一種方式。 uwsgi介紹 uWSGI是一個Web服務器&#xff0c;它實現了WSGI協議、uwsgi、http等協議。Nginx中HttpUwsgiModule的作用是與uWSGI服務器進行交換。 WSGI / …

網絡學習網址

網絡之路博客 http://ccieh3c.com/ 轉載于:https://www.cnblogs.com/changha0/p/8179801.html

路由到另外一個頁面_Nextjs使用解讀一(項目搭建與路由系統)

文章說明&#xff1a;1. 之前想搭建個人博客&#xff0c;由于學習的是react技術棧&#xff0c;所以就到處搜羅資料學了nextjs&#xff0c;配合koa就把博客搭起來了。該系列文章基于我的學習筆記&#xff0c;重新整理了一遍&#xff0c;如果有錯誤之處&#xff0c;還請指正。2. …

微信獲取token -1000

最終翻看微信開發api找到需要去配置IP白名單。只需要配置訪問來源IP即可。 轉載于:https://www.cnblogs.com/yangjinqiang/p/8184663.html

產品技術和管理

為啥純粹為消費者傳遞體驗的活動可以價格不菲&#xff0c;幾為暴利&#xff1f;——談客戶體驗作為客戶價值提升之源 不論產品還是服務&#xff0c;如果能夠為消費者傳遞有益的體驗&#xff0c;其價值就可以在一般的產品服務之上得以體現&#xff1b;附加了體驗的產品&#xff…

Linux 修改系統編碼

linux服務器的字符集設置可能影響到網站頁面出現 “&#xff1f;&#xff1f;&#xff1f;” 等問號亂碼&#xff0c;還有可能導致文件中的漢字部分出現亂碼。有兩個原因 服務器沒有安裝 zh_CN.UTF-8 字符集&#xff0c;導致不支持中文&#xff01;服務器雖然裝了 zh_CN.UTF-8…

jquery ztree 設置勾選_047 JAVA-jQuery

jQuery操作元素屬性的值表單:<body><input type"button" name"" id"but1" value"測試獲得屬性值" /><hr />賬號&#xff1a;<input type"text" name"sxtzh" id"zhanghao" value&q…

Opencv undefined reference to `cv::imread() Ubuntu編譯

Ubuntu下編譯一個C文件&#xff0c;C源程序中使用了opencv&#xff0c;opencv的安裝沒有問題&#xff0c;但是在編譯的過程中出現如下錯誤&#xff1a; undefined reference to cv::imread(std::string const&, int)undefined reference to cv::noArray()undefined referen…