多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內(nèi)最全I(xiàn)T社區(qū)平臺 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁 > php開源 > php教程 > [置頂] splay復(fù)習(xí)小記

[置頂] splay復(fù)習(xí)小記

來源:程序員人生   發(fā)布時間:2016-07-04 12:11:56 閱讀次數(shù):2415次

簡介

splay的原名是舒展樹,1種超級實用的數(shù)據(jù)結(jié)構(gòu),能快速地干很多數(shù)據(jù)結(jié)構(gòu)不能干的事情。
很久之前就聽說過并且稍微地學(xué)了1些,但是當(dāng)時了解地其實不是很多。
有些人把splay達(dá)成spaly叫做死吧你,(⊙﹏⊙)b

結(jié)構(gòu)

實質(zhì)上他是1個2叉搜索樹,就是每一個節(jié)點的左兒子在原序列中是在自己左側(cè)的,右兒子在原序列中是在自己右側(cè)的,構(gòu)圖的方式有很多。
每個節(jié)點都可以存儲1些值,表示它的子樹中的信息(比如說甚么最大值啊,和啊以內(nèi)的)。

構(gòu)圖

fo(i,1,n){ scanf("%d",&a[i+1]);f[i]=i+1; t[i+1][0]=i;update(i+1); } f[n+1]=root=n+2,t[n+2][0]=n+1;update(n+2);

初學(xué)者的構(gòu)圖可以構(gòu)成1條鏈。這樣很明顯左兒子在原序列上的位置是在自己左側(cè)的了。

但是有1個很奇妙的問題,為何從2號點開始建?為何建完點還要再向上多開1個n+2號點?

其實這類打法可以免后面的特判,現(xiàn)在的1號點和n+2號點是建立在首尾兩段的,如果不建立這兩個點2號點的兒子和n+1號點的父親都會指向0并傳遞信息,但是首尾建立1個虛點在修改操作中可以更方便的操作。比如說旋轉(zhuǎn)的時候要觸及到首尾的時候,如果沒有虛點,沒法把首尾單獨(dú)放到1個子樹中去。

其實建成1條鏈在后面的操作會很慢。

int build(int l,int r,int y){ if(l>r)return 0; int mid=(l+r)/2; int x=insert(a[mid]);f[x]=y; if(l==r)return x; tt[x][0]=build(l,mid-1,x); tt[x][1]=build(mid+1,r,x); update(x); return x; } root=build(0,n+1,0);

insert是建點操作,到后面再講。
這樣1開始就建成1顆2叉樹會比較快。

功能

旋轉(zhuǎn)

首先講1個重要的部份,就是旋轉(zhuǎn)。
這里寫圖片描述
其實splay這顆2叉樹的中序遍歷就是原序列,例如圖中的原序列就是:AXBYC。現(xiàn)在我們要把x旋轉(zhuǎn)到y(tǒng)的位置上,但是不能改變這棵樹的中序遍歷(及在原序列的前后順序)。

bool son(int x){ if(tt[f[x]][0]==x)return 0;return 1; } void rotate(int x){ int y=f[x],z=son(x); tt[y][z]=tt[x][1-z]; if(tt[x][1-z])f[tt[x][1-z]]=y;f[x]=f[y]; if(f[y])tt[f[y]][son(y)]=x; f[y]=x;tt[x][1-z]=y; update(y);update(x); }

其實代碼很短(f[x]表示x的父親,tt[x][0]和tt[x][1]分別是x的左右兒子)。
函數(shù)son(x)的作用是分辨x是其父親的左兒子還是右兒子(左兒子是0,右兒子是1)。
當(dāng)把x旋轉(zhuǎn)到y(tǒng)是,y就成為的x的右兒子,但是x原來的右兒子B就沒有地方放了。怎樣辦?我們發(fā)現(xiàn)在原序列中的順序是X < B < Y, 所以B應(yīng)當(dāng)在X的右側(cè)但是在Y的左側(cè),所以現(xiàn)在Y的左兒子應(yīng)當(dāng)是B右兒子不變。如圖所示。
代碼應(yīng)用了1個小技能z和1-z恰好把0和1轉(zhuǎn)化,也能夠打成z和z^1(^是xor,異或)。

將x節(jié)點旋轉(zhuǎn)為y節(jié)點的兒子

void splay(int x,int y){ if(x==y)return; remove(x,y); while(f[x]!=y){ if(f[f[x]]!=y)if(son(f[x])==son(x))rotate(f[x]);else rotate(x); rotate(x); } if(!y)root=x; }

remove是懶標(biāo)記下傳,后面再講。
為何是把x旋轉(zhuǎn)為y的兒子,由于這樣更加方便的操作,比如說要對x的子樹進(jìn)行操作,如果變成把x旋轉(zhuǎn)到y(tǒng)的位置會麻煩很多而且不方便打。
旋轉(zhuǎn)的思路:如果x和x的父親和x的爺爺是1條折線,那末就旋轉(zhuǎn)成不是1個折線,然后像上面將的旋轉(zhuǎn)1樣向上推動。具體的最好自己畫個圖,有益于理解。
1般像splay(x,0)就是把x旋轉(zhuǎn)為根節(jié)點,splay(x,y)就是把x旋轉(zhuǎn)為y的兒子(具體是左兒子還是有兒子根據(jù)原序列中的順序來定)

節(jié)點值的更新

void update(int x){ if(!x)return; t[x].size=1+t[tt[x][0]].size+t[tt[x][1]].size; t[x].sum=key[x]+t[tt[x][0]].sum+t[tt[x][1]].sum; t[x].lda=max(t[tt[x][0]].lda,t[tt[x][0]].sum+key[x]+t[tt[x][1]].lda); t[x].rda=max(t[tt[x][1]].rda,t[tt[x][1]].sum+key[x]+t[tt[x][0]].rda); t[x].mx=max(max(t[tt[x][0]].mx,t[tt[x][1]].mx),t[tt[x][0]].rda+t[tt[x][1]].lda+key[x]); }

當(dāng)x節(jié)點的子節(jié)點變動是就需要更新,具體更新的內(nèi)容據(jù)題目而定。

對1段節(jié)點進(jìn)行操作

x=kth(root,l-1);splay(x,0); y=kth(root,r+1);splay(y,x);

如果要對[l,r]這段區(qū)間進(jìn)行操作。思路:先把這段區(qū)間同時放到1顆子樹上去且這可子樹沒有其他過剩的節(jié)點。
首先如果把l⑴旋轉(zhuǎn)成根節(jié)點,那末[l,n]的節(jié)點都會在l⑴(及root)的左子樹上。然后再把r+1旋轉(zhuǎn)為l⑴的兒子,由于r+1在序列中再l⑴的右側(cè),所以r+1旋轉(zhuǎn)以后1定是l⑴的右兒子,那末在原序列中的順序大于l⑴的,小于r+11定都是現(xiàn)在r+1節(jié)點的左子樹了。
那末現(xiàn)在只要對r+1的左子樹進(jìn)行操作就行了。

printf("%d\n",t[tt[y][0]].mx);

比如要輸出[l,r]段的最大值,上段程序后面只用加上這段程序就能夠了。

插入1個或1段節(jié)點

現(xiàn)在要把a(bǔ)數(shù)組中的數(shù)從posi位置后開始插入進(jìn)序列中。
參照對1段節(jié)點進(jìn)行操作

fo(i,1,k)scanf("%d",&a[i]); x=kth(root,posi);splay(x,0); y=kth(root,posi+1);splay(y,x); tt[y][0]=build(1,k,y);

現(xiàn)在只需要把這k個數(shù)插進(jìn)y的左子樹中就能夠了。build就是上面構(gòu)圖中的build,實質(zhì)就是把a(bǔ)數(shù)組1到k中的節(jié)點插為y的子樹。

刪除1個或1段節(jié)點

現(xiàn)在要從posi這個位置開始刪去k個節(jié)點。
參照對1段節(jié)點進(jìn)行操作

scanf("%d%d",&posi,&k);posi++; x=kth(root,posi⑴);splay(x,0); y=kth(root,posi+k);splay(y,x); del(tt[y][0]); tt[y][0]=0; update(y);update(x);

這里也同理,由于要從posi開始刪節(jié)點,序列的位置要比posi⑴大,比posi+k小。
del函數(shù)是甚么呢?

void del(int x){ if(!x)return; shan[++shan[0]]=x; del(tt[x][0]);del(tt[x][1]); }

由于刪去了1些點,這些點原來的位置不能浪費(fèi)在那里,用1個棧存起來,建點的以后再用。

建點操作

int insert(int x){ int o; if(shan[0])o=shan[shan[0]--];else o=++num;//主要是這行 初始化 .例如:key[o]=t[o].sum=t[o].mx=x;t[o].size=1;根據(jù)題目而定 . . }

為了避免空間的浪費(fèi),如果還有刪除過得節(jié)點的位置空在那里的話,就調(diào)用出來,否則就新建1個位置。

區(qū)間的修改操作

例如從posi開始后的k個點都加上k,參照對1段節(jié)點進(jìn)行操作

x=kth(root,posi⑴);splay(x,0); y=kth(root,posi+k);splay(y,x); change(tt[y][0],zhi); update(y);update(x);

同理。

void change(int x,int y){//這里打的是區(qū)間加操作,據(jù)題目而定 if(!x)return; t[x].sum+=t[x].size*y; key[x]+=y;t[x].add+=y;//add是懶標(biāo)記,用于標(biāo)記下傳 if(y>0)t[x].lda=t[x].rda=t[x].mx=t[x].sum;//這里是某道題的修改,據(jù)題目而定 else t[x].lda=t[x].rda=0,t[x].mx=y; }

懶標(biāo)記下傳

void down(int x){ if(!x)return; if(t[x].add!=maxn){ change(tt[x][0],t[x].add); change(tt[x][1],t[x].add); t[x].add=maxn; } }
void remove(int x,int y){ do{ d[++d[0]]=x; x=f[x]; }while(x!=y); while(d[0])down(d[d[0]--]); }

這類東西支持區(qū)間修改的數(shù)據(jù)結(jié)構(gòu)都要用到的,但是splay中的有所不同,由于只有在旋轉(zhuǎn)的以后才用的到,例如splay(x,y),所以需要把x到y(tǒng)的路徑上的標(biāo)記都下傳。

區(qū)間翻轉(zhuǎn)操作

例如把x的子樹的區(qū)間翻轉(zhuǎn)。

void overturn(int x){ if(!x)return; swap(tt[x][0],tt[x][1]); t[x].biao^=1; }

其實很簡單,只需要把所有節(jié)點的左右兒子調(diào)換便可。注意懶標(biāo)記的biao要用^或1-biao,由于如果某段區(qū)間被同時翻轉(zhuǎn)兩次相當(dāng)于沒有翻轉(zhuǎn)。

查詢序列中第k個位置

int kth(int x,int k){ down(x); if(t[tt[x][0]].size+1==k)return x; if(t[tt[x][0]].size+1>k)return kth(tt[x][0],k); else return kth(tt[x][1],k-t[tt[x][0]].size-1); }

這個很簡單啦。
其實如果想知道第x節(jié)點在序列中的序號的話,可以把x旋轉(zhuǎn)到根(及splay(x,0)),然后t[tt[x][0]].size+1就是x在原序列中的序號。

區(qū)間分離

把x為根節(jié)點的這棵樹以原序列序號y為分水嶺,分成l和r兩顆子樹。

void split(int x,int y,int &l,int &r){ int j=kth(x,y); splay(j,0); l=j,r=t[j][1]; tt[l][1]=0; f[r]=0; update(j); }

區(qū)間合并

把以x為根的樹和以y為根的樹合并為樹l。
為何要找到x樹中第 size[x]大(及在原序列中序號最大的節(jié)點)的節(jié)點,由于在原序列中序號最大的節(jié)點沒有右兒子,方便合并。

void merge(int x,int y,int &l){ int j=kth(x,size[x]); splay(j,0); tt[j][1]=y; f[y]=j; update(j); l=j; }

保護(hù)各種的樹

比如說Link_Cut_Tree(及l(fā)ct或動態(tài)樹)……

由于本人是個蒟蒻

目前也只知道這么多了,但是這些操作在大部份題目中都夠用了。

入門題

【CQOI2014】排序機(jī)械臂
【NOI2005】保護(hù)數(shù)列
甚么最大值,排序也能夠用splay來練練手。

生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 欧美另类精品一区二区三区 | 99久久精品国产综合男同 | 综合亚洲精品一区二区三区 | www.亚洲免费 | 中文乱码一二三四有限公司 | 91精品福利观看 | 日本护士毛片在线视频 | 日本v片免费一区二区三区 日本v视频 | 欧美99视频 | 久久国产一区二区三区 | 日本特黄特色大片免费播放视频 | 在线高清美女视频免费看 | 久操视频网| 波多野结衣资源在线观看 | 天堂mv亚洲mv在线播放9蜜 | 90性后网| 日韩欧美综合在线二区三区 | 在线视频日韩欧美 | 欧美式free群乱 | 日韩精品亚洲一级在线观看 | 黄色天堂在线 | 免费网站看v片在线观看 | 爱爱欧美在线观看视频 | 欧美日韩一区二区综合 | 成人国产欧美精品一区二区 | 国产精品永久免费视频观看 | 亚洲精品中文字幕乱码无线 | 尤物精品 | h网站在线播放 | 一区二区三区影视 | www视频网站 | 国产精品视频久久 | 麻豆福利在线观看 | 自拍中文字幕 | 亚洲日本一区二区三区在线不卡 | 国产91在线九色 | 日韩精品一区二区三区中文字幕 | 日本亚州在线播放精品 | 澳门特级α片免费观看视频 | 国内自拍亚洲 | 亚洲天堂麻豆 |