考试的时候用了两个树状数组去优化,暴力修改,树状数组维护修改后区间差值还有最终求和,最后骗了40分。。
这道题有好多种做法,求和好说,最主要的是开方。这道题过的关键就是掌握一点:在数据范围内,最多开方五六次就会变成1,这样以后再修改就不用修改了。
① 线段树打标记
② 分块打标记
③ 树状数组+并查集
因为我考试的时候用的树状数组,所以直接打的第三种,相对来说代码量也少一些。
思路:开始时父亲都指向自己,如果变成1,就把父亲指向下一个位置即可。修改的时候相当于跳着修改。代码当中会有注解。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; #define pos(i,a,b) for(int i=(a);i<=(b);i++) #define pos2(i,a,b) for(int i=(a);i>=(b);i--) #define N 201000 #define LL long long int n,m; LL c[N],cha[N]; LL a[N]; LL fa[N]; LL lowbit(int x) { return x&(-x); } void add(LL i,LL x) { while(i<=n) { c[i]+=x; i+=lowbit(i); } } LL tot(int i) { LL sum=0; while(i>0) { sum+=c[i]; i-=lowbit(i); } return sum; } LL sum1(int i,int j) { return tot(j)-tot(i-1); } LL find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } int main() { //freopen("god.in","r",stdin); //freopen("god.out","w",stdout); scanf("%d",&n); pos(i,1,n+10) fa[i]=i; pos(i,1,n) { scanf("%lld",&a[i]); add(i,a[i]); if(a[i]<=1) fa[i]=find(find(i)+1);//插入时如果小于等于1,就指向下一位 } scanf("%d",&m); pos(i,1,m) { int k,l,r; scanf("%d%d%d",&k,&l,&r); if(l>r) swap(l,r); if(k==0) { for(LL j=find(l);j<=r;j=find(j+1))//循环时直接跳着循环 { LL tmp=(LL)sqrt(a[j]); add(j,tmp-a[j]);//相当于把节点修改为更改之后的值 a[j]=tmp; if(a[j]<=1) fa[j]=find(j+1);//压缩路径 } } else printf("%lld\n",sum1(l,r)); } //while(1); return 0; }