- 衆所周知,線段樹和樹狀數組是 中非常重要的兩個知識點,幾乎是年年都考。
- 所以,掌握線段樹和樹狀數組是非常的重要!
- 本次例題的難度:提高到省選
(再難我也打不出了)。
咋一看,這題嚇了我們一跳:方差該怎麼維護?
像這種不好直接維護的量,我們通常有兩種方法:
- 把它轉化爲我們好維護的值來維護。
- 強行維護它,考慮修改對它的結果的影響。
在實際編程中,第一種方法往往好打不好想,第二種方法是好想不好打。
比如這題,我們可以把方差的公式化簡一下(記 爲序列 的平均數, 即爲方差):
所以,我們可以得到:
直接開兩個樹狀數組分別維護區間平分和和區間和即可。
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() 函數即快讀函數。
對於 ,我們有兩個非常重要的性質:
於是,我們便把不好維護的 值拆成了兩個我們還是比較好維護的東東。
所以,我們在線段樹上用兩個值域分別維護區間 和與區間 和即可。再維護一個標記 tag
表示整個區間加了 tag
。這樣,我們就可以 在線段樹上轉移,而總的時間複雜度就是 。
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() 函數即快讀函數。