1. 程式人生 > 其它 >hit軟構部落格1--git工具使用的學習

hit軟構部落格1--git工具使用的學習

0.什麼是線段樹

線段樹有著比分塊小的時間複雜度,也有著比樹狀陣列大的通用性,有著比平衡樹更小的常數和程式碼難度,實在是一種非常牛逼的東西。

1.線段樹入門

我們要了解線段樹,首先得明白它長什麼樣。
此處放一張圖:

如圖,一個節點代表一個區間,從中點切開這個區間,就成為了左右區間(不一定嚴謹,聽著吧)。
由於每切一次,區間都減少一半,因此樹高為\(\log n\)級別。
葉節點的值直接由序列得出,其它節點有左右節點合併得出。
為方便,將\(p\)的左右節點分別設為\(p\times2\)\(p\times2+1\)(可以證明這樣節點不會重複,只是犧牲了一點空間)。
詞窮了,直接開始上程式碼吧。

//以下為加減,求和操作
void pushup(int p){
	val[p]=val[p<<1]+val[p<<1|1];
}
void build(int p,int l,int r){//建樹
	if(l==r){
		val[p]=v[l];//v[l]為序列第l項
	}
	int mid=(l+r>>1);
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
	pushup(p);//將左右節點的資訊合併
}

學會了建樹,接下來就應該是操作了。
操作1:單點查詢
簡單到不知道如何解釋了,直接上程式碼。

int query(int p,int l,int r,int pos){//pos為查詢位置
	if(l==r)return val[p];
	int mid=(l+r>>1);
	if(pos<=mid)return query(p<<1,l,mid,pos);
	else return query(p<<1|1,mid+1,r,pos);
}

操作2:單點修改
自上而下到葉節點進行修改,再自下而上更新

void modify(int p,int l,int r,int pos,int x){
	if(l==r){
		val[p]+=x;
		return;
	}
	int mid=(l+r>>1);
	if(pos<=mid)modify(p<<1,l,mid,pos);
	else modify(p<<1,mid+1,r,pos);
}

以上操作暴力都可以做到,現在就要介紹暴力做不到的:區間操作。
首先上簡單點的:區間查詢。
我們想一想,維護非葉節點有什麼用?進行區間操作。
先上程式碼:

void query(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return val[p];//當前區間被完全包含,直接返回
	int mid=(l+r>>1),ans=0;
	if(L<=mid)ans+=query(p<<1,l,mid,L,R);//左兒子與詢問區間有交集
	if(R>mid)ans+=query(p<<1|1,mid+1,r,L,R);//右兒子與詢問區間有交集
}

易證,資訊肯定是不重不漏的。
下面進行時間複雜度證明:
只有包含端點的區間才能一直遞迴下去。
未包含端點有兩種情況:完全在詢問區間內,完全在詢問區間外,這些都是直接返回。
而端點只有兩個,樹高為\(\log n\)級別,因此查詢複雜度也為\(\log n\)

接下來是區間修改。
詢問可以不到葉子,因為只要獲取資訊。但修改不行,因為修改要將資訊更新。
好像思路進入了僵局。
但線段樹有一個強大的功能:懶標記。
有一個標記,代表此節點已被更新,但子節點未被更新。
如果區間被修改區間完全包含,打一個標記,並更新它的資訊。
如果以後需要對此區間的兒子進行操作,就將標記下傳,更新兒子節點的資訊,將兒子節點打上標記,並將此節點的標記去除。
標記下傳複雜度理論為\(O(1)\),因此只是常數大了那麼一點(不是億點)。

void pushdown(int p,int l,int r){
	if(tag[p]){
		int mid=(l+r>>1);
		val[p<<1]+=(mid-l+1)*tag[p];
		val[p<<1|1]+=(r-mid)*tag[p];
		tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];
		tag[p]=0;
	}
}
void modify(int p,int l,int r,int L,int R,int x){
	if(L<=l&&r<=R){
		val[p]+=(r-l+1)*x;tag[p]+=x;return;
	}
	pushdown(p,l,r);int mid=(l+r>>1);
	if(L<=mid)modify(lc,l,mid,L,R,x);
	if(R>mid)modify(rc,mid+1,r,L,R,x);
}

P3372程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100001];
struct tree{
	int l,r;
	long long sum,add;
}tree[400001];
void label(int p){
	if(tree[p].add){
		tree[p*2].sum+=(tree[p*2].r-tree[p*2].l+1)*tree[p].add;
		tree[p*2+1].sum+=(tree[p*2+1].r-tree[p*2+1].l+1)*tree[p].add;
		tree[p*2].add+=tree[p].add;
		tree[p*2+1].add+=tree[p].add;
		tree[p].add=0;
	}
}
void bulid(int l,int r,int p){
	tree[p].l=l,tree[p].r=r;
	if(l==r){
		tree[p].sum=a[l];
		return;
	}
	int mid=(l+r)/2;
	bulid(l,mid,p*2);
	bulid(mid+1,r,p*2+1);
	tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
void change(int l,int r,int p,int num){
	if(tree[p].l>=l&&tree[p].r<=r){
		tree[p].sum+=1ll*num*(tree[p].r-tree[p].l+1);
		tree[p].add+=num;
		return;
	}
	label(p);
	int mid=(tree[p].l+tree[p].r)/2;
	if(l<=mid)change(l,r,p*2,num);
	if(r>mid)change(l,r,p*2+1,num);
	tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
long long ask(int l,int r,int p){
	long long ans=0;
	if(tree[p].l>=l&&tree[p].r<=r){
		return tree[p].sum;
	}
	label(p);
	int mid=(tree[p].l+tree[p].r)/2;
	if(l<=mid)ans+=ask(l,r,p*2);
	if(r>mid)ans+=ask(l,r,p*2+1);
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	bulid(1,n,1);
	while(m--){
		int op,l,r;
		scanf("%d%d%d",&op,&l,&r);
		if(op==1){
			int x;
			scanf("%d",&x);
			change(l,r,1,x);
		}
		else printf("%lld\n",ask(l,r,1));
	}
	return 0;
}

其它操作例如區間最值,其實是大同小異的。