2492 上帝造题的七分钟 2
题目描述 Description
XLk觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。
"第一分钟,X说,要有数列,于是便给定了一个正整数数列。
第二分钟,L说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。
第三分钟,k说,要能查询,于是便有了求一段数的和的操作。
第四分钟,彩虹喵说,要是noip难度,于是便有了数据范围。
第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。
第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过64位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"
——《上帝造题的七分钟·第二部》
所以这个神圣的任务就交给你了。
输入描述 Input Description
第一行一个整数n,代表数列中数的个数。
第二行n个正整数,表示初始状态下数列中的数。
第三行一个整数m,表示有m次操作。
接下来m行每行三个整数k,l,r,k=0表示给[l,r]中的每个数开平方(下取整),k=1表示询问[l,r]中各个数的和。
UPD:注意数据中有可能l>r,所以遇到这种情况请交换l和r。
输出描述 Output Description
对于询问操作,每行输出一个回答。
样例输入 Sample Input
10
1 2 3 4 5 6 7 8 9 10
5
0 1 10
1 1 10
1 1 5
0 5 8
1 4 8
样例输出 Sample Output
19
7
6
数据范围及提示 Data Size & Hint
对于30%的数据,1<=n,m<=1000,数列中的数不超过32767。
对于100%的数据,1<=n,m<=100000,1<=l,r<=n,数列中的数大于0,且不超过1e12。
注意l有可能大于r,遇到这种情况请交换l,r。
来源:Nescafe 20
【思路】
区间查询+打标记。
一个关键的思想就是一个数被反复开方并向下取整最终会变为1,如果一个数变成1就不需要对它操作了。
对于区间开方操作,对于区间中的每一个点都进行操作如果为1则下次不再操作。
实现1:
BIT+“并查集”。
【代码1】
1 //341ms 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream> 6 #define FOR(a,b,c) for(int a=(b);a<=(c);a++) 7 using namespace std; 8 9 typedef long long LL; 10 const int maxn = 200000+10; 11 12 LL a[maxn],p[maxn]; 13 int n,m; 14 15 LL read() { 16 char c=getchar(); 17 while(!isdigit(c)) c=getchar(); 18 LL x=0; 19 while(isdigit(c)) { 20 x=x*10+c-'0'; 21 c=getchar(); 22 } 23 return x; 24 } 25 26 LL c[maxn]; 27 int lowbit(int x) { return x&(-x); } 28 LL Sum(LL x) { 29 LL res=0; 30 while(x>0) { 31 res += c[x]; 32 x-=lowbit(x); 33 } 34 return res; 35 } 36 void Add(int x,LL d) { 37 while(x<=n) { 38 c[x] += d; 39 x += lowbit(x); 40 } 41 } 42 int find(int x) { 43 return x==p[x]?x:p[x]=find(p[x]); 44 } 45 46 int main() { 47 n=read(); 48 FOR(i,1,n) a[i]=read(),Add(i,a[i]); 49 FOR(i,1,n+1) p[i]=i; //n+1为哨兵 50 m=read(); 51 LL u,v,w; 52 FOR(i,1,m) { 53 w=read(),u=read(),v=read(); 54 if(u>v) swap(u,v); 55 56 if(w) 57 printf("%lld ",Sum(v)-Sum(u-1)); 58 else 59 { 60 for(int j=find(u);j<=v;j=find(j+1)) { 61 LL s=sqrt(a[j]); 62 Add(j,s-a[j]); 63 a[j]=s; 64 if(s==1) { 65 p[j]=j+1; 66 if(j==v) break; 67 } 68 } 69 } 70 } 71 return 0; 72 }
实现2:
线段树+结点标记
【代码2】
1 //399ms 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream> 6 #define FOR(a,b,c) for(int a=(b);a<=(c);a++) 7 using namespace std; 8 9 typedef long long LL; 10 const int maxn = 500000+10; 11 12 LL a[maxn],p[maxn]; 13 int n,m; 14 15 LL read() { 16 char c=getchar(); 17 while(!isdigit(c)) c=getchar(); 18 LL x=0; 19 while(isdigit(c)) { 20 x=x*10+c-'0'; 21 c=getchar(); 22 } 23 return x; 24 } 25 26 LL sumv[maxn]; 27 bool f[maxn]; 28 void build(int u,int L,int R) { 29 int lc=u*2,rc=u*2+1; 30 if(L==R) { 31 sumv[u]=a[L]; 32 } 33 else { 34 int M=L+(R-L)/2; 35 build(lc,L,M); 36 build(rc,M+1,R); 37 sumv[u]=sumv[lc]+sumv[rc]; 38 } 39 } 40 int ya,yb; 41 void update(int u,int L,int R) { 42 int lc=u*2,rc=u*2+1; 43 if(f[u]) return ; 44 if(L==R) { 45 sumv[u]=sqrt(sumv[u]); 46 if(sumv[u]==1) f[u]=1; 47 } 48 else { 49 int M=L+(R-L)/2; 50 if(ya<=M) update(lc,L,M); 51 if(M<yb) update(rc,M+1,R); 52 53 sumv[u]=sumv[lc]+sumv[rc]; 54 f[u]=f[lc]&&f[rc]; 55 } 56 } 57 LL query(int u,int L,int R) { 58 int lc=u*2,rc=u*2+1; 59 if(ya<=L && R<=yb) { 60 return sumv[u]; 61 } 62 else { 63 int M=L+(R-L)/2; 64 LL res=0; 65 if(ya<=M) res+=query(lc,L,M); 66 if(M<yb) res+=query(rc,M+1,R); 67 return res; 68 } 69 } 70 71 int main() { 72 n=read(); 73 FOR(i,1,n) a[i]=read(); 74 build(1,1,n); 75 m=read(); 76 int w; 77 while(m--) { 78 w=read(),ya=read(),yb=read(); 79 if(ya>yb) swap(ya,yb); 80 if(w) printf("%lld ",query(1,1,n)); 81 else update(1,1,n); 82 } 83 return 0; 84 }