loj6277.数列分块入门 1
维护序列,支持区间修改,单点查询。
直接分块,修改整块打标记,散块暴力修改。查询直接点值加上所在块的标记即可。
时间复杂度 \(O(n\sqrt n)\)。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,block,a[50001],b[50001],tag[50001];
inline int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
int main()
{
n=read();
block=sqrt(n);
for(register int i=1;i<=n;++i)
{
a[i]=read();
b[i]=(i-1)/block+1;
}
for(register int i=1;i<=n;++i)
{
int opt=read(),l=read(),r=read(),x=read();
if(opt==0)
{
int bl=(l-1)/block+1,br=(r-1)/block+1;
if(bl==br)
{
for(register int j=l;j<=r;++j)
a[j]+=x;
continue;
}
for(register int j=bl+1;j<br;++j)
tag[j]+=x;
for(register int j=l;j<=bl*block;++j)
a[j]+=x;
for(register int j=(br-1)*block+1;j<=r;++j)
a[j]+=x;
}
if(opt==1)
printf("%d\n",a[r]+tag[(r-1)/block+1]);
}
return 0;
}
loj6278.数列分块入门 2
维护序列,支持区间加,区间查询小于一个数的个数。
先思考怎样维护答案。可以分块后对每个块维护一个 \(vector\),里面是块内排序后的结果。
修改的时候整块直接打标记,散块暴力重构一遍。
查询整块用 lower_bound,散块暴力查询。
块大小 \(\sqrt n\) 时,复杂度 \(O(n\sqrt n\log n)\),还不够优秀。
考虑调整块大小。设块大小为 \(B\) 发现每次操作的运算次数是 \(B\log n+\frac n B\geq 2\sqrt{n\log n}\),取等条件是 \(B\log n=\frac n B\),解得 \(B=\sqrt{\frac n{\log n}}\),此时复杂度优化为 \(O(n\sqrt{n\log n})\),实测结果比前一种快了一半以上。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
int n,block,a[500001],b[500001],tag[500001];
vector<int> v[500001];
inline int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void rebuild(int k,int l,int r,int x)
{
v[k].clear();
for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
a[i]+=tag[k];
for(register int i=l;i<=r;++i)
a[i]+=x;
for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
v[k].push_back(a[i]);
sort(v[k].begin(),v[k].end());
tag[k]=0;
}
inline int query(int k,int l,int r,int x)
{
int res=0;
for(register int i=l;i<=r;++i)
res+=(a[i]+tag[k])<x;
return res;
}
signed main()
{
n=read();
block=sqrt(n/log2(n));
for(register int i=1;i<=n;++i)
{
a[i]=read();
b[i]=(i-1)/block+1;
v[b[i]].push_back(a[i]);
}
for(register int i=1;i<=b[n];++i)
sort(v[i].begin(),v[i].end());
for(register int i=1;i<=n;++i)
{
int opt=read(),l=read(),r=read(),x=read();
int bl=(l-1)/block+1,br=(r-1)/block+1;
if(opt==0)
{
if(bl==br)
{
rebuild(bl,l,r,x);
continue;
}
for(register int j=bl+1;j<br;++j)
tag[j]+=x;
rebuild(bl,l,block*bl,x);
rebuild(br,block*(br-1)+1,r,x);
}
if(opt==1)
{
x*=x;
if(bl==br)
{
printf("%lld\n",query(bl,l,r,x));
continue;
}
int ans=0;
for(register int j=bl+1;j<br;++j)
ans+=lower_bound(v[j].begin(),v[j].end(),x-tag[j])-v[j].begin();
printf("%lld\n",ans+query(bl,l,block*bl,x)+query(br,block*(br-1)+1,r,x));
}
}
return 0;
}
loj6279.数列分块入门 3
维护序列,支持区间加,区间查询前驱。
和上一题完全没区别,只是改一下查询的东西即可。
但是这一题为啥块长 \(\sqrt n\) 更快了啊感觉好神秘。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
#define int long long
int n,a[100001],b[100001],block,tag[100001];
vector<int> v[100001];
inline int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void rebuild(int k,int l,int r,int x)
{
v[k].clear();
for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
a[i]+=tag[k];
for(register int i=l;i<=r;++i)
a[i]+=x;
for(register int i=block*(k-1)+1;i<=min(block*k,n);++i)
v[k].push_back(a[i]);
tag[k]=0;
sort(v[k].begin(),v[k].end());
}
inline int query(int k,int l,int r,int x)
{
int res=-1ll<<60;
for(register int i=l;i<=r;++i)
if(a[i]+tag[k]<x&&a[i]+tag[k]>res)
res=a[i]+tag[k];
return res;
}
signed main()
{
n=read();
block=sqrt(n);
for(register int i=1;i<=n;++i)
{
a[i]=read();
b[i]=(i-1)/block+1;
v[b[i]].push_back(a[i]);
}
for(register int i=1;i<=b[n];++i)
sort(v[i].begin(),v[i].end());
for(register int i=1;i<=n;++i)
{
int opt=read(),l=read(),r=read(),x=read();
int bl=(l-1)/block+1,br=(r-1)/block+1;
if(opt==0)
{
if(bl==br)
{
rebuild(bl,l,r,x);
continue;
}
for(register int j=bl+1;j<br;++j)
tag[j]+=x;
rebuild(bl,l,bl*block,x);
rebuild(br,(br-1)*block+1,r,x);
}
if(opt==1)
{
int ans=-1ll<<60;
if(bl==br)
{
ans=query(bl,l,r,x);
printf("%lld\n",ans==-1ll<<60? -1:ans);
continue;
}
for(register int j=bl+1;j<br;++j)
{
int pos=lower_bound(v[j].begin(),v[j].end(),x-tag[j])-v[j].begin()-1;
if(pos==-1)
continue;
if(v[j][pos]+tag[j]>ans)
ans=v[j][pos]+tag[j];
}
ans=max(ans,max(query(bl,l,bl*block,x),query(br,(br-1)*block+1,r,x)));
printf("%lld\n",ans==-1ll<<60? -1:ans);
}
}
return 0;
}
loj6280.数列分块入门 4
维护序列,支持区间加,区间求和。
在第一题的基础上多维护一个整块的和就好了。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define int long long
int n,block,a[50001],b[50001],tag[50001],sum[50001];
inline int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
signed main()
{
n=read();
block=sqrt(n);
for(register int i=1;i<=n;++i)
{
a[i]=read();
b[i]=(i-1)/block+1;
sum[b[i]]+=a[i];
}
for(register int i=1;i<=n;++i)
{
int opt=read(),l=read(),r=read(),x=read();
int bl=(l-1)/block+1,br=(r-1)/block+1;
if(opt==0)
{
if(bl==br)
{
for(register int j=l;j<=r;++j)
a[j]+=x;
sum[bl]+=x*(r-l+1);
continue;
}
for(register int j=bl+1;j<br;++j)
{
tag[j]+=x;
sum[j]+=x*block;
}
for(register int j=l;j<=bl*block;++j)
{
a[j]+=x;
sum[bl]+=x;
}
for(register int j=(br-1)*block+1;j<=r;++j)
{
a[j]+=x;
sum[br]+=x;
}
}
if(opt==1)
{
int ans=0;
++x;
if(bl==br)
{
for(register int j=l;j<=r;++j)
ans=(ans+(a[j]+tag[bl])%x)%x;
printf("%lld\n",ans);
continue;
}
for(register int j=bl+1;j<br;++j)
ans=(ans+sum[j]%x)%x;
for(register int j=l;j<=bl*block;++j)
ans=(ans+(a[j]+tag[bl])%x)%x;
for(register int j=(br-1)*block+1;j<=r;++j)
ans=(ans+(a[j]+tag[br])%x)%x;
printf("%lld\n",ans);
}
}
return 0;
}
loj6281.数列分块入门 5
维护数列,支持区间开方,区间求和。
经典套路就是开方开不了几次就到 1 了,然后就不用处理了,直接维护整块最大值和整块和即可。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define int long long
int n,block,a[50001],b[50001],maxn[50001],sum[50001];
inline int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void rebuild(int k,int l,int r)
{
for(register int i=l;i<=r;++i)
a[i]=sqrt(a[i]);
sum[k]=maxn[k]=0;
for(register int i=(k-1)*block+1;i<=min(k*block,n);++i)
{
sum[k]+=a[i];
maxn[k]=max(maxn[k],a[i]);
}
}
signed main()
{
n=read();
block=sqrt(n);
for(register int i=1;i<=n;++i)
{
a[i]=read();
b[i]=(i-1)/block+1;
maxn[b[i]]=max(maxn[b[i]],a[i]);
sum[b[i]]+=a[i];
}
for(register int i=1;i<=n;++i)
{
int opt=read(),l=read(),r=read();
read();
int bl=(l-1)/block+1,br=(r-1)/block+1;
if(opt==0)
{
if(bl==br)
{
rebuild(bl,l,r);
continue;
}
for(register int j=bl+1;j<br;++j)
if(maxn[j]>1)
rebuild(j,(j-1)*block+1,j*block);
rebuild(bl,l,bl*block);
rebuild(br,(br-1)*block+1,r);
}
if(opt==1)
{
int ans=0;
if(bl==br)
{
for(register int j=l;j<=r;++j)
ans+=a[j];
printf("%lld\n",ans);
continue;
}
for(register int j=bl+1;j<br;++j)
ans+=sum[j];
for(register int j=l;j<=bl*block;++j)
ans+=a[j];
for(register int j=(br-1)*block+1;j<=r;++j)
ans+=a[j];
printf("%lld\n",ans);
}
}
return 0;
}