题目链接:https://loj.ac/problem/6281
#6281. 数列分块入门 5
内存限制:256 MiB时间限制:500 ms标准输入输出
题目描述
给出一个长为 nn 的数列 a_1\ldots a_na1…an,以及 nn 个操作,操作涉及区间开方,区间求和。
输入格式
第一行输入一个数字 nn。
第二行输入 nn 个数字,第 ii 个数字为 a_iai,以空格隔开。
接下来输入 nn 行询问,每行输入四个数字 \mathrm{opt}, l, r, copt,l,r,c,以空格隔开。
若 \mathrm{opt} = 0opt=0,表示将位于 [l, r][l,r] 的之间的数字都开方。对于区间中每个 a_i(l\le i\le r),\: a_i ← \left\lfloor \sqrt{a_i}\right\rfloorai(l≤i≤r),ai←⌊ai⌋
若 \mathrm{opt} = 1opt=1,表示询问位于 [l, r][l,r] 的所有数字的和。
输出格式
对于每次询问,输出一行一个数字表示答案。
样例
样例输入
4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4
样例输出
6
2
思路:
这里有个小思维,因为数的大小小于2^31,最多开方5次就为1。所以我们做法和上面一样有sum数组维护整块的值,
计算整块的时候,用flag记录整组都为1的情况,如果全为1就加block(块的大小),不是就暴力加,不会超时的,因为最多5次开方
就全为1了,时间复杂度和上面的一样(5倍对时间复杂度没关系)
ac代码:
#include<iostream>
#include<cmath>
#include<string.h>
#include<vector>
#include<set>
using namespace std;
const int maxn=50000+5;
typedef long long ll;
ll v[50000+5],pos[50000+5],flag[50000];//pos保存分块情况,flag存储块更新情况
ll n,m;
ll s[50000];//维护各块的和
void update(int l,int r){//更新
for(int i=l;i<=min(pos[l]*m,(ll)r);i++){//左边不完整块,暴力加
s[pos[l]]-=v[i];
v[i]=sqrt(v[i]);
s[pos[l]]+=v[i];
}
if(pos[l]!=pos[r]){//右边不完整块,暴力加
for(int i=(pos[r]-1)*m+1;i<=r;i++){
s[pos[r]]-=v[i];
v[i]=sqrt(v[i]);
s[pos[r]]+=v[i];
}
}
for(int i=pos[l]+1;i<=pos[r]-1;i++){
if(flag[i]) continue;
else{
flag[i]=1;//先假设全为1,后面如果不是改为0
for(int j=(i-1)*m+1;j<=i*m;j++){
s[i]-=v[j];
v[j]=sqrt(v[j]);
s[i]+=v[j];
if(v[j]!=1)
flag[i]=0;
}
}
}
}
void query(int l,int r){
ll ans=0;
for(int i=l;i<=min(pos[l]*m,(ll)r);i++){//左边不完整快,暴力加
ans+=v[i];
}
if(pos[l]!=pos[r]){//右边不完整块,暴力加
for(int i=(pos[r]-1)*m+1;i<=r;i++){
ans+=v[i];
}
}
for(int i=pos[l]+1;i<=pos[r]-1;i++){
ans+=s[i];
}
cout<<ans<<endl;
}
int main(){
cin>>n;
m=sqrt(n);//分成m块
memset(flag,0,sizeof(flag));
for(int i=1;i<=n;i++){
cin>>v[i];
}
for(int i=1;i<=n;i++){
pos[i]=(i-1)/m+1;//分块操作
s[pos[i]]+=v[i];//计算每一块的数值和
}
int opt,l,r,c;
for(int i=1;i<=n;i++){
cin>>opt>>l>>r>>c;
if(opt==0){
update(l,r);
}
else{
query(l,r);
}
}
return 0;
}