本期是数列分块入门<2>。该系列的所有题目来自hzwer在LOJ上提供的数列分块入门系列。
Blog:http://hzwer.com/8053.html sto hzwer orz %%% [转载]
好像上面的链接↑打不开,放一个转载:https://www.cnblogs.com/ve-2021/articles/9135559.html
------------------------------------------------------------------------------------------------------------------------
LOJ-P6279:
接着第二题的解法,其实只要把块内查询的二分稍作修改即可。
不过这题其实想表达:可以在块内维护其它结构使其更具有拓展性,比如放一个set,这样如果还有插入、删除元素的操作,会更加的方便。
#include <bits/stdc++.h>
using namespace std;
const int maxn=10000S;
int a[maxn],idx[maxn],tag[maxn],tot=1000;
set<int> st[10S];
void change(int l,int r,int c){
for(int i=l;i<=min(idx[l]*tot,r);i++){
st[idx[l]].erase(a[i]);
a[i]+=c;
st[idx[l]].insert(a[i]);
}
if(idx[l]!=idx[r]){
for(int i=(idx[r]-1)*tot+1;i<=r;i++){
st[idx[r]].erase(a[i]);
a[i]+=c;
st[idx[r]].insert(a[i]);
}
}
for(int i=idx[l]+1;i<=idx[r]-1;i++)
tag[i]+=c;
}
int query(int l,int r,int c){
int ans=-1;
for(int i=l;i<=min(idx[l]*tot,r);i++){
int val=a[i]+tag[idx[l]];
if(val<c)
ans=max(val,ans);
}
if(idx[l]!=idx[r]){
for(int i=(idx[r]-1)*tot+1;i<=r;i++){
int val=a[i]+tag[idx[r]];
if(val<c)
ans=max(val,ans);
}
}
for(int i=idx[l]+1;i<=idx[r]-1;i++){
int x=c-tag[i];
set<int>::iterator itr=st[i].lower_bound(x);
if(itr==st[i].begin())
continue;
--itr;
ans=max(ans,*itr+tag[i]);
}
return ans;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++){
idx[i]=(i-1)/tot+1;
st[idx[i]].insert(a[i]);
}
for(int i=1;i<=n;i++){
int opt,l,r,c;
cin>>opt>>l>>r>>c;
if(opt==0)
change(l,r,c);
if(opt==1)
cout<<query(l,r,c)<<endl;
}
return 0;
}
LOJ-P6280:
这题的询问变成了区间上的询问,不完整的块还是暴力;而要想快速统计完整块的答案,需要维护每个块的元素和,先要预处理一下。
考虑区间修改操作,不完整的块直接改,顺便更新块的元素和;完整的块类似之前标记的做法,直接根据块的元素和所加的值计算元素和的增量。
#include <bits/stdc++.h>
using namespace std;
int idx[50005],tot;
long long a[50005],tag[50005],sum[50005];
void change(int l,int r,int c){
for(int i=l;i<=min(idx[l]*tot,r);i++){
a[i]+=c;
sum[idx[l]]+=c;
}
if(idx[l]!=idx[r]){
for(int i=(idx[r]-1)*tot+1;i<=r;i++){
a[i]+=c;
sum[idx[r]]+=c;
}
}
for(int i=idx[l]+1;i<=idx[r]-1;i++)
tag[i]+=c;
}
long long query(int l,int r){
long long ans=0;
for(int i=l;i<=min(idx[l]*tot,r);i++)
ans+=a[i]+tag[idx[l]];
if(idx[l]!=idx[r]){
for(int i=(idx[r]-1)*tot+1;i<=r;i++)
ans+=a[i]+tag[idx[r]];
}
for(int i=idx[l]+1;i<=idx[r]-1;i++)
ans+=sum[i]+tot*tag[i];
return ans;
}
int main(){
int n;
cin>>n;
tot=sqrt(n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++){
idx[i]=(i-1)/tot+1;
sum[idx[i]]+=a[i];
}
for(int i=1;i<=n;i++){
int opt,l,r,c;
cin>>opt>>l>>r>>c;
if(opt==O)
change(l,r,c);
if(opt==1)
cout<<query(l,r)%(c+1)<<endl;
}
return 0;
}
LOJ-P6281:
稍作思考可以发现,开方操作比较棘手,主要是对于整块开方时,必须要知道每一个元素,才能知道他们开方后的和,也就是说,难以快速对一个块信息进行更新。
看来我们要另辟蹊径。不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成或者。
如果每次区间开方只不涉及完整的块,意味着不超过个元素,直接暴力即可。
如果涉及了一些完整的块,这些块经过几次操作以后就会都变成或,于是我们采取一种分块优化的暴力做法,只要每个整块暴力开方后,记录一下元素是否都变成了或,区间修改时跳过那些全为或的块即可。
这样每个元素至多被开方不超过次,显然复杂度没有问题。
#include <bits/stdc++.h>
using namespace std;
int a[50005],sum[50005],idx[50005],tot;
bool flag[50005];
void solve(int x){
if(flag[x])
return;
flag[x]=1;
sum[x]=0;
for(int i=(x-1)*tot+1;i<=x*tot;i++){
a[i]=sqrt(a[i]);
sum[x]+=a[i];
if(a[i]>1)
flag[x]=0;
}
}
void change(int l,int r,int c){
for(int i=l;i<=min(idx[l]*tot,r);i++){
sum[idx[l]]-=a[i];
a[i]=sqrt(a[i]);
sum[idx[l]]+=a[i];
}
if(idx[l]!=idx[r]){
for(int i=(idx[r]-1)*tot+1;i<=r;i++){
sum[idx[r]]-=a[i];
a[i]=sqrt(a[i]);
sum[idx[r]]+=a[i];
}
}
for(int i=idx[l]+1;i<=idx[r]-1;i++)
solve(i);
}
int query(int l,int r){
int ans=0;
for(int i=l;i<=min(idx[l]*tot,r);i++)
ans+=a[i];
if(idx[l]!=idx[r]){
for(int i=(idx[r]-1)*tot+1;i<=r;i++)
ans+=a[i];
}
for(int i=idx[l]+1;i<=idx[r]-1;i++)
ans+=sum[i];
return ans;
}
int main(){
int n;
cin>>n;
tot=sqrt(n);
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++){
idx[i]=(i-1)/tot+1;
sum[idx[i]]+=a[i];
}
for(int i=1;i<=n;i++){
int opt,l,r,c;
cin>>opt>>l>>r>>c;
if(opt==0)
change(l,r,c);
if(opt==l)
cout<<query(l,r)<<endl;
}
return 0;
}
友情提醒:不要Ctrl C+Ctrl V