前言 \color{green}{\texttt{前言}} 前言
- 作为提高必备的两大数据结构,线段树和树状数组非常重要。本日记将举一些线段树和树状数组的实例,带大家了解线段树和树状数组的作用。
- 本日记的题目难度大概在 提 高 + / 省 选 \color{red}{提高+/省选} 提高+/省选左右。不会讲线段树和树状数组的原理。
实 例 \color{green}{实例} 实例
洛 谷 P 1471 方 差 \color{blue}{洛谷P1471\ \ 方差} 洛谷P1471 方差
【
题
意
】
:
\color{orange}{【题意】:}
【题意】: 蒟蒻HansBug
在一本数学书里面发现了一个神奇的数列,包含
N
N
N个实数。他想算算这个数列的平均数和方差。数据带修改。
【注】:方差:方差是计算一个数列数据波动情况的重要量,记为
s
2
s^2
s2。记数据为
A
A
A,
P
P
P为平均数,则方差计算公式为:
s
2
=
∑
i
=
1
n
(
A
i
−
P
)
2
n
s^2=\frac{\sum\limits_{i=1}^{n} (A_i-P)^2}{n}
s2=ni=1∑n(Ai−P)2
【 思 路 】 : \color{orange}{【思路】:} 【思路】: 我们把方差的公式稍微修改一下,得:
(以上图片选自一篇洛谷题解)
所以,现在我们只需维护区间的平方和与区间和即可。
而对于区间的平方和,如果我们令区间同时加上一个数
x
x
x,我们有:
(以上图片依旧选自一篇洛谷题解)
然后,我们就可以直接用线段树维护求解。
【 代 码 】 : \color{orange}{【代码】:} 【代码】:
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
char c=0;int x=0;bool f=0;
while (!g(c)) f=c=='-',c=gc;
while (g(c)) x=x*10+c-48,c=gc;
return f?-x:x;
}
const int N=1e5+100;
double sum[N<<2],add[N<<2];
double seg[N<<2],a[N];
inline void pushup(int o){
sum[o]=sum[o<<1]+sum[o<<1|1];
seg[o]=seg[o<<1]+seg[o<<1|1];
}
inline void pushdown(int o,int l,int r){
double tag=add[o];add[o]=0;
add[o<<1|1]+=tag;add[o<<1]+=tag;
register int mid=(l+r)>>1;//注意顺序,一定要先修改seg,再修改sum
seg[o<<1]+=2*tag*sum[o<<1]+(mid-l+1)*tag*tag;
seg[o<<1|1]+=2*tag*sum[o<<1|1]+(r-mid)*tag*tag;
sum[o<<1]+=tag*(mid-l+1);sum[o<<1|1]+=tag*(r-mid);
}
void build(int o,int l,int r){
add[o]=0.0;
if (l==r){
sum[o]=a[l];
seg[o]=a[r]*a[r];
return;
}
register int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushup(o);return;
}
void updata(int o,int l,int r,int p,int q,double v){
if (l>q||r<p) return;
if (p<=l&&r<=q){
seg[o]+=2*v*sum[o]+(r-l+1)*v*v;
sum[o]+=v*(r-l+1);add[o]+=v;return;
}
if (add[o]) pushdown(o,l,r);
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_sum(int o,int l,int r,int p,int q){
if (l>q||r<p) return 0.0;
if (p<=l&&r<=q) return sum[o];
if (add[o]) pushdown(o,l,r);
register int mid=(l+r)>>1;
register double answer=0.0;
answer+=query_sum(o<<1,l,mid,p,q);
answer+=query_sum(o<<1|1,mid+1,r,p,q);
return answer;
}
double query_squ(int o,int l,int r,int p,int q){
if (l>q||r<p) return 0.0;
if (p<=l&&r<=q) return seg[o];
if (add[o]) pushdown(o,l,r);
register int mid=(l+r)>>1;
register double answer=0.0;
answer+=query_squ(o<<1,l,mid,p,q);
answer+=query_squ(o<<1|1,mid+1,r,p,q);
return answer;
}
int n,m,opt,l,r;
int main(){
freopen("t1.in","r",stdin);
n=read();m=read();
for(int i=1;i<=n;i++)
scanf("%lf",&a[i]);
build(1,1,n);
for(int i=1;i<=m;i++){
opt=read();l=read();r=read();
if (opt==1){
register double v;
scanf("%lf",&v);
updata(1,1,n,l,r,v);
}
else{
double ave=query_sum(1,1,n,l,r)/(r-l+1.0);
if (opt==2) printf("%.4lf\n",ave);
else printf("%.4lf\n",-ave*ave+query_squ(1,1,n,l,r)/(r-l+1));
}
}
return 0;
}
洛 谷 P 1558 色 板 游 戏 \color{blue}{洛谷P1558\ \ 色板游戏} 洛谷P1558 色板游戏
【
题
意
】
:
\color{orange}{【题意】:}
【题意】: 阿宝
上学了,今天老师拿来了一块很长的涂色板。
色板长度为 L L L, L L L是一个正整数,所以我们可以均匀地将它划分成 L L L块 1 1 1厘米长的小方格。并从左到右标记为 1 , 2 , . . . L 1, 2, ... L 1,2,...L。
现在色板上只有一个颜色,老师告诉阿宝在色板上只能做两件事:
C A B C
指在 A A A到 B B B 号方格中涂上颜色 C C C。P A B
指老师的提问: A A A到 B B B号方格中有几种颜色。
学校的颜料盒中一共有
T
T
T 种颜料。为简便起见,我们把他们标记为
1
,
2
,
.
.
.
T
1, 2, ... T
1,2,...T。 开始时色板上原有的颜色就为
1
1
1号色。 面对如此复杂的问题,阿宝
向你求助,你能帮助他吗?
【 思 路 】 : \color{orange}{【思路】:} 【思路】: 乍一看这题很难,但是 1 ≤ T ≤ 30 1 \leq T \leq 30 1≤T≤30,所以我们可以把区间每个位置的颜色用二进制保存,然后就可以用线段树维护了。
【 代 码 】 : \color{orange}{【代码】:} 【代码】:
const int N=1e5+100;
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
char c=0;int x=0;bool f=0;
while (!g(c)) f=c=='-',c=gc;
while (g(c)) x=x*10+c-48,c=gc;
return f?-x:x;
}
int sum[N<<2],add[N<<2];
inline void pushup(int o){
sum[o]=sum[o<<1]|sum[o<<1|1];
}
inline void pushdown(int o){
int tag=add[o];add[o]=0;
add[o<<1]=tag;add[o<<1|1]=tag;
sum[o<<1]=tag;sum[o<<1|1]=tag;
}
void build(int o,int l,int r){
if (l==r){
sum[o]=1;return;
}
register int mid=(l+r)>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
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){
add[o]=1<<(v-1);
sum[o]=1<<(v-1);
return;
}
if (add[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;
}
int query(int o,int l,int r,int p,int q){
if (l>q||r<p) return 0;
if (p<=l&&r<=q) return sum[o];
if (add[o]) pushdown(o);
register int mid=(l+r)>>1;
register int answer=0;
answer|=query(o<<1,l,mid,p,q);
answer|=query(o<<1|1,mid+1,r,p,q);
return answer;
}
int n,m,color;//n:L;m:O;color:T
int calc(int x){//计算x的二进制中有多少个1
register int cnt=0;
for(int i=0;i<color;i++)
if (x&(1<<i))
cnt++;
return cnt;
}
int main(){
n=read();color=read();
m=read();build(1,1,n);
for(int i=1;i<=m;i++){
register char opt;cin>>opt;
register int l=read(),r=read();
if (l>r) swap(l,r);//Don't forget it!!!
if (opt=='C'){
register int v=read();
updata(1,1,n,l,r,v);
}
else printf("%d\n",calc(query(1,1,n,l,r)));
}
return 0;
}
洛 谷 P 2184 贪 婪 大 陆 \color{blue}{洛谷P2184\ \ 贪婪大陆} 洛谷P2184 贪婪大陆
【
题
意
】
:
\color{orange}{【题意】:}
【题意】: 面对蚂蚁们的疯狂进攻,小FF
的Tower defence
宣告失败……人类被蚂蚁们逼到了Greed Island
上的一个海湾。现在,小FF
的后方是一望无际的大海, 前方是变异了的超级蚂蚁。小FF
还有大好前程,他可不想命丧于此, 于是他派遣手下最后一批改造SCV
布置地雷以阻挡蚂蚁们的进攻。
小FF
最后一道防线是一条长度为
N
N
N的战壕,小FF
拥有无数多种地雷,而SCV
每次可以在
[
L
,
R
]
[ L , R ]
[L,R]区间埋放同一种不同于之前已经埋放的地雷。 由于情况已经十万火急,小FF在某些时候可能会询问你在
[
L
′
,
R
′
]
[ L' , R']
[L′,R′] 区间内有多少种不同的地雷, 他希望你能尽快的给予答复。
【 思 路 】 : \color{orange}{【思路】:} 【思路】: 每次埋地雷,我们可以看作以下两个事件:
- 在
L
L
L放入一个
(
,表示一种新地雷的开始 - 在
R
R
R放入一个
)
,表示一种地雷的结束位置
对于查询操作,我们先查询
R
R
R以前有多少中地雷,即查询
R
R
R以前有多少个(
,当然有些地雷可能不在
[
L
,
R
]
[L,R]
[L,R]内,它们有一个共性:即结束位置
<
L
<L
<L,所以我们用
R
R
R以前的(
的数量减去
L
L
L以前的)
的数量。
于是,我们分别开两个树状数组维护(
和)
即可。总的时间复杂度:
O
(
M
×
log
N
)
O(M \times \log N)
O(M×logN)。
【 代 码 】 : \color{orange}{【代码】:} 【代码】:
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
char c=0;int x=0;bool f=0;
while (!g(c)) f=c=='-',c=gc;
while (g(c)) x=x*10+c-48,c=gc;
return f?-x:x;
}
const int N=1e5+1e3;
typedef long long ll;
ll c[N][3];int n,m,opt,l,r;
inline int F(int x){
return x&(-x);
}
inline void updata(int x,int p){
for(;x<=n;x+=F(x)) c[x][p]++;
}
inline ll query(int x,int p){
register ll ans=0ll;
for(;x;x-=F(x))
ans+=c[x][p];
return ans;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
opt=read();l=read();r=read();
if (opt==1){
updata(l,1);
updata(r,2);
}
else printf("%lld\n",query(r,1)-query(l-1,2));
}
return 0;
}