P4145 上帝造题的七分钟 2 / 花神游历各国
题目大意:
本体总共就两个操作:
\(1.\)为将\(l\)至\(r\)区间内的所有数开根,及\(a_i=\sqrt a_i\);
\(2.\)输出\(l\)至\(r\)区间内的所有数的和
思路:
既然是区间修改和区间查询,那是个蒟蒻都能想到用线段树来维护,但我们可以发现,若一个根节点的子树开方了,那它的和也变得不确定。因此不能用懒标记来实现区间修改。可单点修改的复杂度为\(n^2\),一看就会超。但是,我们又双叒叕发现一个数顶多开\(5\)次方就会变为\(1\)或\(0\)( \(\sqrt {\sqrt{\sqrt{\sqrt{\sqrt{\sqrt {10^{12}}}}}}} \approx 1\) )而正好\(\sqrt 1=1\),所以我们只需要再维护一个区间最大值,在进行更新时看是否大于\(1\),若大于的话才需要更新
完整代码:
#include<bits/stdc++.h>
#define lc p<<1
#define rc p<<1|1
#define int long long
using namespace std;
const int N=101000;
int a[N];
struct edge{int sum,max;
}tr[4*N];
inline int read(){int x=0,w=1;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';return x*w;
}
void build(int p,int l,int r){if(l==r){tr[p].sum=a[l];tr[p].max=a[l];return;}int mid=(l+r)>>1;build(lc,l,mid);build(rc,mid+1,r);tr[p].sum=tr[lc].sum+tr[rc].sum;tr[p].max=max(tr[lc].max,tr[rc].max);
}
void update(int p,int l,int r,int ll,int rr){if(l==r && ll<=l && r<=rr){tr[p].sum=sqrt(tr[p].sum);tr[p].max=tr[p].sum;return;}int mid=(l+r)>>1;if(mid>=ll && tr[lc].max>1)update(lc,l,mid,ll,rr);if(mid<rr && tr[rc].max>1)update(rc,mid+1,r,ll,rr);tr[p].sum=tr[lc].sum+tr[rc].sum;tr[p].max=max(tr[lc].max,tr[rc].max);
}
int query(int p,int l,int r,int ll,int rr){if(ll<=l && r<=rr){return tr[p].sum;}int res=0;int mid=(l+r)>>1;if(mid>=ll)res+=query(lc,l,mid,ll,rr);if(mid<rr)res+=query(rc,mid+1,r,ll,rr);return res;
}
signed main(){int n;cin>>n;for(int i=1;i<=n;++i){a[i]=read();}build(1,1,n);int m;cin>>m;int k,l,r;for(int i=1;i<=m;++i){k=read(),l=read(),r=read();if(l>r)swap(l,r);if(k==0)update(1,1,n,l,r);else printf("%lld\n",query(1,1,n,l,r));}
}
线段树讲解