線段樹 Segment_treenode
網上有人把線段樹翻譯成 Interval_Treeios
Interval_Tree 是另一種數據結構 並且並不是二叉樹
算法
這個是線段樹的標準E文翻譯數組
能夠看wikipedia的原文 http://en.wikipedia.org/wiki/Segment_tree數據結構
顧名思義 線段樹存儲的是連續的線段而非離散的節點函數
先看一張經典的線段樹圖解post
這個就是標準的線段樹ui
既然是樹形結構 咱們就得先考慮怎麼存儲這棵樹spa
分析線段樹的定義翻譯
*線段樹是一棵二叉樹 記爲T(a, b)
*參數a,b表示區間[a,b] 其中b-a稱爲區間的長度 記爲L
*線段樹T(a,b)也可遞歸定義爲
-若L>1 [a, (a+b) div 2]爲T的左兒子
[(a+b) div 2,b]爲T的右兒子
-若L=1 T爲葉子節點
能夠獲得一些基本性質
*線段樹除最後一層外是滿二叉樹
*線段樹是平衡的 高度是Log2L左右
如此咱們有2種存儲方法
*直接用指針
定義節點
struct node{ int L,r; int color; }post[N<<2];
其中ls rs分別爲左右兒子 l,r是區間的範圍
真正實現時通常用數組模擬指針
咱們只需定義longint數組ls[]rs[] l[] r[]
*用*2和*2+1代替左右兒子指針
因爲是除最後一層外是滿二叉樹
咱們能夠向存儲堆同樣存儲線段樹
用l[]r[]來存儲節點區間範圍
x的左右兒子分別就是x*2和x*2+1
具體實現用位移代替乘2
這樣乘法指針運算和上述數組調用同樣 幾乎不須要時間
具體用哪一種純粹是我的喜愛 沒什麼區別
(下文中個人程序都是用的數組模擬 直接存儲兒子指針)
接下來討論線段樹的具體操做
也就是維護這種數據結構的算法 (srO 數據結構+算法=程序 Orz)
總結起來就兩個詞 遞歸 & 分治
結合一個具體問題吧 PKU 2777
http://poj.org/problem?id=2777
這是線段樹的入門題 至關經典
要求程序實現一個塗色的程序
支持對區間[A,B]塗C的顏色和統計區間[A,B]的顏色種類
樸素的作法是用數組a[]存儲下整個區間[0,100000]
而後循環塗色 循環查詢 這樣的複雜度是N*N 大大地TLE
咱們考慮用線段樹處理這個區間問題
首先咱們得建樹
先看程序
void Build(int L,int r,int id){ post[id].L=L; post[id].r=r; post[id].color=1; if(L!=r){ int mid=(L+r)>>1; Build(L,mid,id<<1); Build(mid+1,r,id<<1|1); } }*build函數是一個遞歸的過程 參數L,r表示當前創建區間[L,r]的節點
* L!=r 是遞歸的邊界條件 即創建到葉子節點了
*根據線段樹定義 分別遞歸創建左右兒子區間
-2*id,2*id+1分別爲當前節點的左子樹和右子樹
-注意使用位運算提升效率 還需注意L r mid 皆爲區間端點
其實上文中建好的線段樹實際上是一個骨架
就至關於樸素作法中咱們還未操做的空數組 等待咱們給它刷顏色
既然要刷顏色 咱們就得存儲各區間的顏色 給每一個節點新開一個域n來記錄顏色
表如今數組模擬上就是新建數組n[]
n數組表明當前節點所表明區間的顏色
由於這個問題的染色是覆蓋類型的染色
對一個區間染色天然把爲當前區間的子區間也染色了
因此是對子樹染色而非區間染色
接着這樣的思路 咱們能夠寫出以下程序
int mid=(post[id].r+post[id].L)>>1; if(post[id].L==L&&post[id].r==r){ post[id].color=color; return; } if(post[id].color==color) return; if(post[id].color>0){ post[id<<1].color=post[id<<1|1].color=post[id].color; post[id].color=0; }*判斷當前區間是否在須要覆蓋的區間內 是就修改顏色
這裏須要說明一下這種寫法的正確性
即不會出現[L,r]在[l[x],r[x]]外與當前區間沒有交集的狀況
首先在根節點處[L,r]和區間顯然有交集
而後運用數學概括法的思路 說明當前節點區間和[L,r]有交集的時候 遞歸插入兒子也是保證和兒子區間有交集的
這樣只要執行插入函數就有交集 就能保證程序正確性
給出全部和當前區間有交集的狀況圖 能夠發現通過if語句判斷 遞歸插入都保證仍是和兒子區間有交集
(黑色爲當前區間 紅色爲欲染色區間 一共6種狀況)
不難分析出這個插入函數的複雜度是O(N)級別的(須要遍歷子樹) 從常數上看比樸素還慢
可是不覆蓋子樹上的區間又會產生錯誤 咱們須要對插入進行改進
改進後 咱們的n[]數組不單記錄一個節點的顏色 而是記錄的子樹的顏色
咱們看具體操做
*若是當前區間已經染色且顏色和欲染色一致 則直接退出(這句話能夠不要)
*若是當前區間被徹底覆蓋 就說明子樹也被徹底覆蓋了 直接給當前節點染色退出
*若是沒有被徹底覆蓋
-就給先給左右兒子染色成當前節點的顏色 而後當前節點賦值爲混合顏色=0
-而後再遞歸染色左右子樹
這樣修改徹底覆蓋的區間時就能夠直接修改而後退出 不用遍歷子樹了
而沒有徹底覆蓋時 須要把顏色先下傳給左右子樹 再遞歸修改 保證子樹顏色的正確性
這樣咱們訪問的區間總數就降到了O(LogN)級別個 比O(N)好了很多
這個實際上是一種最原始的Lazy-Tag思想
這種思想很重要 也比較難掌握 咱們之後詳細討論
給出改進後的代碼
void update(int L,int r,int color,int id){ int mid=(post[id].r+post[id].L)>>1; if(post[id].L==L&&post[id].r==r){ post[id].color=color; return; } if(post[id].color==color) return; if(post[id].color>0){ post[id<<1].color=post[id<<1|1].color=post[id].color; post[id].color=0; } if(r<=mid) update(L,r,color,id<<1); else if(L>mid) update(L,r,color,id<<1|1); else{ update(L,mid,color,id<<1); update(mid+1,r,color,id<<1|1); } }最後就是統計了
統計相對很簡單 一共30種顏色 用個Simple Hash便可
這時候咱們記錄的混合顏色就有用了 用於判斷
結構和插入差很少 不過遞歸的條件再也不是是否有交集而是是否爲空節點了
void query(int L,int r,int id){ int mid=(post[id].L+post[id].r)>>1; if(post[id].color>0){ visit[post[id].color]=1; return; } if(r<=mid) query(L,r,id<<1); else if(L>mid) query(L,r,id<<1|1); else{ query(L,mid,id<<1); query(mid+1,r,id<<1|1); } }
最後是個人AC代碼#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define N 450000 struct node{ int L,r; int color; }post[N<<2]; bool visit[50]; void Build(int L,int r,int id){ post[id].L=L; post[id].r=r; post[id].color=1; if(L!=r){ int mid=(L+r)>>1; Build(L,mid,id<<1); Build(mid+1,r,id<<1|1); } } void update(int L,int r,int color,int id){ int mid=(post[id].r+post[id].L)>>1; if(post[id].L==L&&post[id].r==r){ post[id].color=color; return; } if(post[id].color==color) return; if(post[id].color>0){ post[id<<1].color=post[id<<1|1].color=post[id].color; post[id].color=0; } if(r<=mid) update(L,r,color,id<<1); else if(L>mid) update(L,r,color,id<<1|1); else{ update(L,mid,color,id<<1); update(mid+1,r,color,id<<1|1); } } void query(int L,int r,int id){ int mid=(post[id].L+post[id].r)>>1; if(post[id].color>0){ visit[post[id].color]=1; return; } if(r<=mid) query(L,r,id<<1); else if(L>mid) query(L,r,id<<1|1); else{ query(L,mid,id<<1); query(mid+1,r,id<<1|1); } } int main(){ int L,v,n,a,b,c,i,j; char tmp[3]; int sum=0; while(scanf("%d%d%d",&L,&v,&n)!=EOF){ Build(1,L,1); for(i=0;i<n;i++){ scanf("%s",tmp); if(tmp[0]=='P'){ scanf("%d%d",&a,&b); sum=0; memset(visit,0,sizeof(visit)); query(a,b,1); for(j=1;j<=v;j++) if(visit[j]) sum++; printf("%d\n",sum); }else{ scanf("%d%d%d",&a,&b,&c); update(a,b,c,1); } } } }