题目:BZOJ3038.
题目大意:给定一个长度为
n
n
n的序列
a
a
a,支持区间开方与区间求和操作
m
m
m次.
1
≤
n
≤
1
0
5
,
1
≤
a
i
≤
1
0
12
1\leq n\leq 10^5,1\leq a_i\leq 10^{12}
1≤n≤105,1≤ai≤1012.
经典题,当然可以用线段树或者分块做,但我们这里介绍一种树状数组+并查集的做法.
首先考虑一下每个数最多开方 O ( log log a i ) O(\log \log a_i) O(loglogai)次变为 1 1 1,变为 1 1 1之后就不需要任何操作了.
考虑利用这个性质,在每次区间开方的时候大力枚举区间内每一个位置,若碰到 1 1 1直接跳过.
至于如何实现跳过其实很好办,我们对每个位置建立一个并查集,位置
p
p
p所属并查集的根表示
p
p
p后面(包括
p
p
p本身)第一个值不为
1
1
1的位置,即:
r
o
t
[
p
]
=
min
i
∈
[
p
,
n
]
∩
a
i
>
1
{
i
}
rot[p]=\min_{i\in[p,n]\cap a_i>1} \{i\}
rot[p]=i∈[p,n]∩ai>1min{i}
然后就可以用树状数组直接维护单点修改区间查询了,时间复杂度 O ( n log n log log n ) O(n\log n\log\log n) O(nlognloglogn).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000;
int n,m;
LL a[N+9],c[N+9];
void Add(int p,LL v){for (;p<=n;p+=p&-p) c[p]+=v;}
LL Query(int p){LL res=0;for (;p;p-=p&-p) res+=c[p];return res;}
LL Query(int L,int R){return Query(R)-Query(L-1);}
int fa[N+9];
int Find(int k){return k^fa[k]?fa[k]=Find(fa[k]):k;}
Abigail into(){
scanf("%d",&n);
for (int i=1;i<=n;++i){
scanf("%lld",&a[i]);
Add(i,a[i]);
}
}
Abigail work(){
for (int i=1;i<=n+1;++i) fa[i]=i;
}
Abigail getans(){
scanf("%d",&m);
for (int i=1;i<=m;++i){
int opt,l,r;
scanf("%d%d%d",&opt,&l,&r);
if (l>r) swap(l,r);
if (opt==2){
for (int j=Find(l);j<=r;j=Find(++j)){
LL t=sqrt(a[j]);
Add(j,t-a[j]);
a[j]=t;
if (a[j]==1) fa[Find(j)]=Find(j+1);
}
}else printf("%lld\n",Query(l,r));
}
}
int main(){
into();
work();
getans();
return 0;
}