線段樹(一) _概述 基本操做

線段樹 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);
			}
		}
	}
}
相關文章
相關標籤/搜索