线段树解法
本题对区间求根号,因为不存在 s q r t ( ∑ i = 1 n a i ) = ∑ i = 1 n s q r t ( a i ) sqrt(\sum_{i=1}^{n}a_{i})=\sum_{i=1}^{n}sqrt(a_{i}) sqrt(∑i=1nai)=∑i=1nsqrt(ai) 的优秀性质,所以每一次的区间修改都要递归到叶子节点,也无法打出懒标记。因为父亲节点的值需要子节点的值来确定,无法一次性算出。
可行性解释
那这种递归到叶节点的修改方式复杂度岂不是爆炸?还不如不建树呢?其实不是这样的,对于一个 i n t int int 范围内的整数,因为每次开方后向下取整,其开方次数不会大于 6 6 6 次便会使这个数的值为1,便不需要再开方了。那么每个数至多开方 6 6 6 次。所以我们可以将每个节点赋予一个值 v v v ,代表这个区间有多少个 1 1 1 。如果这个区间的1的数量等于这个区间的数的数量,说明该区间内全是 1 1 1 ,则不需要为这个区间进行开方操作,直接返回即可。
具体实现
看代码吧。
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,m;
struct node{
ll l,r,w,v;//w为区间和,v为1数量和
}t[maxn<<2];
ll a[maxn];
inline void pushup(int k)
{//同时对父亲节点两个数据进行更新
t[k].w=t[k<<1].w+t[k<<1|1].w;
t[k].v=t[k<<1].v+t[k<<1|1].v;
}
inline void build(int k,int l,int r)
{
t[k].l=l,t[k].r=r;
if(l==r){
t[k].w=a[l];
t[k].v=(t[k].w==1)?1:0;//初始判断是否有1
return;
}
int mid=l+r>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
pushup(k);
}
inline void update(int k,int l,int r)
{
int x=t[k].l,y=t[k].r;
if(t[k].v==y-x+1)return;//区间全为1,不用更新了,直接返回
if(x==y){//得更新到叶子节点
t[k].w=sqrt(t[k].w);
t[k].v=(t[k].w==1)?1:0;
return;
}
int mid=x+y>>1;
if(l<=mid)update(k<<1,l,r);
if(mid<r)update(k<<1|1,l,r);
pushup(k);
}
inline ll query(int k,int l,int r)
{
int x=t[k].l,y=t[k].r;
if(l<=x&&y<=r)return t[k].w;
int mid=x+y>>1;
ll ans=0;
if(l<=mid)ans+=query(k<<1,l,r);
if(mid<r)ans+=query(k<<1|1,l,r);
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
build(1,1,n);
cin>>m;
for(int i=1;i<=m;i++){
int op,x,y;
cin>>op>>x>>y;
if(x>y){
int tmp=x;x=y;y=tmp;
}
if(!op){
update(1,x,y);
}
else{
cout<<query(1,x,y)<<"\n";
}
}
return 0;
}