GSS问题(二)
仍然是线段树的应用模板题,非常经典
题面
\(n\)个数,\(n\leqslant1e5\),和\(\leqslant10^{18}\),全是自然数
翻译:long long能过
给出两种操作:
- 区间开方\(\rightarrow\)将区间每一个数单独开方,下取整
- 区间求和\(\rightarrow\)将区间所有书加起来求和
作为模板题,我自己给出一个条件:最好用线段树做[doge]
原题还有这样一句话:
mmp这个造数据的比我还懒
对于细节处理问题,比如上面这张图所出现的问题就不再讲,主要是看如何用线段树实现主要操作
思路
显然,开方这个操作并不能直接对于整个区间进行
那咋办嘛?
那只能单个元素开方了
每次操作都对每一个元素进行开方,非常费时间,
但是我们知道开方可以使一个数非常快的变小,直至1,除非本来就是0或者1
那么到了0或1的时候,就无须再开方,
不妨这样考虑:
对于每一个树节点,开设变量\(maxn\)来保存区间最大值,如果区间最大值为1或0,显然整个区间就无须开方,直接跳过,大大优化了复杂度
#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long
#define ci const int &
using namespace std;
const int N=100005;
ll a[N];
ll sum[N<<2],maxn[N<<2];
inline void build(ci k,ci l,ci r){
if(l==r){
sum[k]=a[l];
maxn[k]=a[l];
return ;
}int mid=l+r>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
sum[k]=sum[k<<1]+sum[k<<1|1];
maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}
inline void insert(ci k,ci l,ci r,ci x,ci y){
if(maxn[k]<=1) return ;
if(l==r){
sum[k]=(ll)(sqrt(sum[k]));
maxn[k]=(ll)(sqrt(maxn[k]));
return ;
}int mid=l+r>>1;
if(x<=mid) insert(k<<1,l,mid,x,y);
if(mid<y) insert(k<<1|1,mid+1,r,x,y);
sum[k]=sum[k<<1]+sum[k<<1|1];
maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}
inline ll query(ci k,ci l,ci r,ci x,ci y){
if(x<=l&&r<=y) return sum[k];
int mid=l+r>>1;
ll ret=0;
if(x<=mid) ret+=query(k<<1,l,mid,x,y);
if(mid<y) ret+=query(k<<1|1,mid+1,r,x,y);
return ret;
}int n,m;
int cnt;
int main(){
while(scanf("%d",&n)!=EOF){
printf("Case #%d:\n",++cnt);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
build(1,1,n);
scanf("%d",&m);
while(m--){
int n1,n2,n3;
scanf("%d%d%d",&n1,&n2,&n3);
if(n2>n3) swap(n2,n3);
if(!n1)
insert(1,1,n,n2,n3);
if(n1)
printf("%lld\n",query(1,1,n,n2,n3));
}puts("");
}
return 0;
}