<题目链接>
题目大意:
给定一段序列,现在对指定区间进行两种操作:一是对指定区间进行修改,对其中的每个数字都开根号(开根号后的数字仍然取整);二是对指定区间进行查询,查询这段区间所有数字的和。
解题分析:
本题虽然是区间修改,但是不需要用 lazy标记,因为要对指定区间的每个数进行开根号的处理,也就是说,每次 update ,都要延伸到该区间涉及到的叶子节点,进行开根,而不是在叶子节点上端的某个节点就将开根的指令存储下来。那么是不是说我们每次只能对 update 的每个区间所涉及到的每个节点进行暴力的单点修改呢?很显然不是的,因为每个节点的值不超过2^63,所以每个值的有效开方次数并不多。所以我们对线段树的每个节点引入一个标记cnt,用它来记录该节点对应的区域是否全部不需要开方,如果不需要开方,那么就直接return ,终止无效更新,从而提高效率。
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> using namespace std; #define Lson rt<<1,l,mid #define Rson rt<<1|1,mid+1,r const int M=100000+5; typedef long long ll; ll n,m,arr[M]; struct Tree{ ll sum,cnt; //cnt标记该区间是否需要继续向下开根 }tr[M<<2]; void Pushup(int rt){ if(tr[rt<<1].cnt==0&&tr[rt<<1|1].cnt==0)tr[rt].cnt=0; //如果这一整个区间全部不用开根号,则cnt置为0 else tr[rt].cnt=1; tr[rt].sum=tr[rt<<1].sum+tr[rt<<1|1].sum; } void build(int rt,int l,int r){ tr[rt].cnt=1; //cnt=1代表这段区间需要开根号 if(l==r){ tr[rt].sum=arr[l]; return; } int mid=(l+r)>>1; build(Lson); build(Rson); Pushup(rt); } void update(int rt,int l,int r,int L,int R){ if(tr[rt].cnt==0)return; //如果rt节点所对应的区间全部不需要再开根,就直接return,不需要继续向下查询 if(l==r){ tr[rt].sum=(int)sqrt(tr[rt].sum*1.0); if(tr[rt].sum==1)tr[rt].cnt=0; //如果这个叶子节点的值变为1,那么以后就可以不用开根了,因为1开根号还是1 return; } int mid=(l+r)>>1; if(L<=mid)update(Lson,L,R); if(R>mid)update(Rson,L,R); Pushup(rt); } ll query(int rt,int l,int r,int L,int R){ if(L<=l&&r<=R){ return tr[rt].sum; } int mid=(l+r)>>1; ll ans=0; if(L<=mid)ans+=query(Lson,L,R); if(R>mid)ans+=query(Rson,L,R); return ans; } int main(){ int ncase=0; while(scanf("%lld",&n)!=EOF){ for(int i=1;i<=n;i++){ scanf("%lld",&arr[i]); } build(1,1,n); scanf("%lld",&m); printf("Case #%d:\n",++ncase); for(int i=1;i<=m;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); if(b>c)swap(b,c); //这里一定要注意,如果输入的b>c,则应该交换一下位置 if(!a)update(1,1,n,b,c); else{ printf("%lld\n",query(1,1,n,b,c)); } } printf("\n"); } return 0; }
2018-09-23