题目大意:给定一个序列,提供下列操作:
1.将[l.r]区间内每个数a[i]变为sqrt(a[i])
2.查询[l,r]区间的和
根号是不支持区间修改的,于是我们选择单点修改区间查询的树状数组,但是这样是O(n^2)的,怎么办?
我们发现一个数x最多开loglogx次根号就会变为1 也就是一个int范围内的数只要开5次根号就会变为1 于是修改的总时间复杂度为O(nloglogn)
但是单次修改怎么办?我们维护一个并查集,一旦一个数为1或0,我们就把这个位置的father设为它右面的那个位置即可 这样可以均摊O(n)时间找到一个数后面第一个>1的数
此外此题加入读入优化可以快一倍 令人很费解0.0
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define M 100100 using namespace std; typedef long long ll; int n,m,a[M],fa[M]; ll c[M]; inline int getc() { static const int L = 1 << 15; static char buf[L], *S = buf, *T = buf; if (S == T) { T = (S = buf) + fread(buf, 1, L, stdin); if (S == T) return EOF; } return *S++; } inline int getint() { int c; while(!isdigit(c = getc()) && c != '-'); bool sign = c == '-'; int tmp = sign ? 0 : c - '0'; while(isdigit(c = getc())) tmp = (tmp << 1) + (tmp << 3) + c - '0'; return sign ? -tmp : tmp; } int Find(int x) { if(!fa[x]||fa[x]==x) return fa[x]=x; return fa[x]=Find(fa[x]); } void Update(int x,int y) { for(;x<=n;x+=x&-x) c[x]+=y; } ll Get_Ans(int x) { ll re=0; for(;x;x-=x&-x) re+=c[x]; return re; } void Modify(int x,int y) { int i=x; for( i=x ; i<=y ; i=Find(i+1) ) { int temp=(int)sqrt(a[i]); Update(i,temp-a[i]); a[i]=temp; if(a[i]<=1) fa[i]=Find(i+1); } } int main() { int i,p,x,y; cin>>n; for(i=1;i<=n;i++) { a[i]=getint(); if(a[i]==1||!a[i]) fa[i]=i+1; Update(i,a[i]); } m=getint(); for(i=1;i<=m;i++) { p=getint(); x=getint(); y=getint(); if(p==1) printf("%lld\n", Get_Ans(y)-Get_Ans(x-1) ); else Modify(x,y); } }
原文地址:http://blog.csdn.net/popoqqq/article/details/39961591