2020.05.17日常總結(樹狀數組和線段樹實例)

前言\color{green}{\texttt{前言}}

  • 衆所周知,線段樹和樹狀數組是 OI\texttt{OI} 中非常重要的兩個知識點,幾乎是年年都考。
  • 所以,掌握線段樹和樹狀數組是非常的重要!
  • 本次例題的難度:提高到省選 (再難我也打不出了)

洛谷P5142 區間方差\color{green}{\texttt{洛谷P5142 區間方差}}

【題意】:\color{blue}{\texttt{【題意】:}}

在這裏插入圖片描述

【思路】:\color{blue}{\texttt{【思路】:}}

咋一看,這題嚇了我們一跳:方差該怎麼維護?

像這種不好直接維護的量,我們通常有兩種方法:

  • 把它轉化爲我們好維護的值來維護。
  • 強行維護它,考慮修改對它的結果的影響。

在實際編程中,第一種方法往往好打不好想,第二種方法是好想不好打。

比如這題,我們可以把方差的公式化簡一下(記 xx 爲序列 a1..na_{1..n} 的平均數,s2s^2 即爲方差):

ns2=i=1n(aix)2=i=1n(ai22aix+x2)=i=1nai22nx2+nx2=i=1nai2x2 \begin{aligned} ns^2 &= \sum\limits_{i=1}^{n} (a_i - x)^2\\ &= \sum\limits_{i=1}^{n} (a_i^2-2a_ix+x^2)\\ &=\sum\limits_{i=1}^{n}a_i^2- 2nx^2+nx^2\\ &=\sum\limits_{i=1}^{n}a_i^2-x^2 \end{aligned}

所以,我們可以得到:

s2=i=1nai2ni=1nain2s^2=\dfrac{\sum\limits_{i=1}^{n} a_i^2}{n}-\dfrac{\sum\limits_{i=1}^{n} a_i}{n^2}

直接開兩個樹狀數組分別維護區間平分和和區間和即可。

【代碼】:\color{blue}{\texttt{【代碼】:}}

const int mod=1e9+7;
const int N=1e5+100;
typedef long long ll;
int n,m,a[N];//a:記錄序列 
struct Binary_Indexed_Tree{
	ll sum[N];//樹狀數組前綴和 
	Binary_Indexed_Tree(){
		memset(sum,0,sizeof(sum));
	}//自動初始化操作(是不是賊6~) 
	inline int F(int x){return x&(-x);}
	inline void updata(int x,ll t){//修改操作 
		for(;x<=n;x+=F(x)) sum[x]=(sum[x]+t)%mod;
	}//讓a[x]變成a[x]+t(注意需要實時取模) 
	inline ll query(int x){//注意類型可能需要ll 
		register ll ans=0ll;//注意別漏了初始化 
		for(;x;x-=F(x)) ans=(ans+sum[x])%mod;
		return ans;//ans:前x個數的和對mod取模的值 
	}//query(x):求a[1]+...+a[x],返回膜mod的結果 
}c1,c2;//c1:平方和,c2:原數和(用結構體封裝樹狀數組) 
inline int sqr(int x){
	return 1ll*x*x%mod;
}
inline int ksm(int a,int b){
	register int res=1;
	while (b){
		if (b&1) res=1ll*res*a%mod;
		a=1ll*a*a%mod;b>>=1;
	}
	return res;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){
		register int x=read();
		c1.updata(i,sqr(a[i]=x));
		c2.updata(i,a[i]%mod);
	}
	for(int i=1,opt,l,r;i<=m;i++){
		opt=read();l=read();r=read();
		if (opt==1){//操作1:修改 
			c1.updata(l,(1ll*sqr(r)-sqr(a[l])+mod)%mod);
			c2.updata(l,(1ll*r-a[l]+mod)%mod);a[l]=r;
		}
		else{//否則就是操作2:查詢 
			if (l==r){printf("0\n");continue;}//注意特判 
			int t1=(1ll*c1.query(r)-c1.query(l-1)+mod)%mod;
			int t2=(1ll*c2.query(r)-c2.query(l-1)+mod)%mod;
			int ans=1ll*sqr(t2)*sqr(ksm(r-l+1,mod-2))%mod;
			ans=((1ll*t1*ksm(r-l+1,mod-2)%mod-ans)%mod+mod)%mod;
			printf("%d\n",ans);
		}
	}
	return 0;
}

溫馨提示:read() 函數即快讀函數。

洛谷P6327 區間加區間sin和\color{green}{\texttt{洛谷P6327 區間加區間sin和}}

【題意】:\color{blue}{\texttt{【題意】:}}

在這裏插入圖片描述

【思路】:\color{blue}{\texttt{【思路】:}}

對於 sin\sin,我們有兩個非常重要的性質:

sin(x+v)=sinxcosv+cosxsinvcos(x+v)=cosxcosvsinxsinv \begin{aligned} \sin (x+v) &= \sin x \cos v + \cos x \sin v\\ \cos (x+v) &= \cos x \cos v - \sin x \sin v \end{aligned}

於是,我們便把不好維護的 sin\sin 值拆成了兩個我們還是比較好維護的東東。

所以,我們在線段樹上用兩個值域分別維護區間 sin\sin 和與區間 cos\cos 和即可。再維護一個標記 tag 表示整個區間加了 tag。這樣,我們就可以 O(1)O(1) 在線段樹上轉移,而總的時間複雜度就是 O(n×logn+m×logn)O(n \times \log n+m \times \log n)

【代碼】:\color{blue}{\texttt{【代碼】:}}

const int N=2e5+100;//序列最大長度 
struct Segment_tree{//封裝線段樹模板 
	double sine[N<<2],cose[N<<2];
	long long tag[N<<2];//注意類型 
	inline void pushup(int o){
		sine[o]=sine[o<<1]+sine[o<<1|1];
		cose[o]=cose[o<<1]+cose[o<<1|1];
	}
	void modify(int o,double sinx,double cosx){
		double sint=sine[o],cost=cose[o];
		sine[o]=sint*cosx+sinx*cost;
		cose[o]=cost*cosx-sinx*sint;
	}
	inline void pushdown(int o){
		long long add=tag[o];tag[o]=0;
		modify(o<<1,sin(add),cos(add));
		modify(o<<1|1,sin(add),cos(add));
		tag[o<<1]+=add;tag[o<<1|1]+=add;
	}
	void build(int o,int l,int r,int a[]){
		if (l==r){
			sine[o]=sin(a[l]);
			cose[o]=cos(a[l]);
			return;
		}
		register int mid=(l+r)>>1;
		build(o<<1,l,mid,a);
		build(o<<1|1,mid+1,r,a);
		pushup(o);return;
	}
	void updata(int o,int l,int r,int p,int q,int v){
		if (l>q||r<p) return;//無交集,直接返回 
		if (p<=l&&r<=q){//完全包括 
			modify(o,sin(v),cos(v));
			tag[o]+=v;return;
		}
		if (tag[o]) pushdown(o);
		register int mid=(l+r)>>1;
		updata(o<<1,l,mid,p,q,v);
		updata(o<<1|1,mid+1,r,p,q,v);
		pushup(o);return;
	}
	double query(int o,int l,int r,int p,int q){
		if (p<=l&&r<=q) return sine[o];
		if (tag[o]) pushdown(o);//標記下傳 
		register int mid=(l+r)>>1;//計算中點 
		register double ans=0.0;//注意初始化 
		if (p<=mid) ans+=query(o<<1,l,mid,p,q);
		if (q>mid) ans+=query(o<<1|1,mid+1,r,p,q);
		return ans;//千萬不要忘了返回ans 
	}
}tree;int n,m,a[N];
int main(){
	n=read();//輸入序列的長度 
	for(int i=1;i<=n;i++)
		a[i]=read();
	tree.build(1,1,n,a);
	m=read();//輸入操作的個數 
	for(int i=1,opt,l,r;i<=m;i++){
		opt=read();l=read();r=read();
		if (opt==1) tree.updata(1,1,n,l,r,read());
		else printf("%.1lf\n",tree.query(1,1,n,l,r));
	}
	return 0;
}

溫馨提示:
1. sin 和 cos 函數在 C++ 的 cmath 頭文件內哦。
2. read() 函數即快讀函數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章