题解:P13977 数列分块入门 2
guoshengyu1231 · · 题解
思路
题目要求对一个序列进行一下两种操作:
- 将位于
[l, r] 的之间的数字都加c 。 - 询问
[l, r] 中,小于c^2 的数字的个数。\\
并且本题需要一种高效的算法来同时完成这两种操作,这里使用分块。
什么是分块
分块是一种非常优雅的暴力,通过对数列进行分段,然后对每段单独求解,对每段的答案合并就是最终的答案。
-
-
# 如何建块 通常情况下,在建块时,我们需要完成以下任务: - 确定块的长度和数量。 - 确定每个块的左右边界。 - 标记每个元素属于那个块。 - 维护每个块的信息。
具体要维护那些信息,每到题目都是不一样的,比如这题,我们需要对块内的元素排序,以便来二分查找。但是前三条都是必要的,具体代码如下:
const int maxn=1e6+5;
const int maxm=1e3+5;
int n,m,a[maxn];
int in[maxn],L[maxm],R[maxm],tag[maxm];
int len,cnt;//块长和块数
vector<int> v[maxm];//每个块内排序后的元素
void resort(int x)//对每个块重新排序
{
v[x].clear();
for(int i=L[x];i<=R[x];i++) v[x].push_back(a[i]);
sort(v[x].begin(),v[x].end());
}
void init()
{
len=cnt=sqrt(n);
if(len*cnt<n) cnt++;//计算块长和块数
for(int i=1;i<=cnt;i++)
{
L[i]=R[i-1]+1;
R[i]=i*len;
}
R[cnt]=n;//左右边界计算
for(int i=1;i<=cnt;i++)
{
for(int j=L[i];j<=R[i];j++) in[j]=i;//标记每个元素属于那个块。
resort(i);
}
}
建完了块之后,我们就需要想如何实现题目要求的两种操作。
区间修改
对于不完整的块,直接在原数组中修改即可。但如果时完整的块,我们再去依次修改就很消耗时间,这时候我们就需要用到线段树中懒标记的思想,用一个数组
那么完整代码如下:
void add(int l,int r,int x)
{
int p=in[l],q=in[r];
if(p==q)//特判在同一个块内的情况
{
for(int i=l;i<=r;i++) a[i]+=x;
resort(p);//重新排序
}
else
{
for(int i=l;i<=R[p];i++) a[i]+=x;
for(int i=L[q];i<=r;i++) a[i]+=x;
resort(p);resort(q); //重新排序
for(int i=p+1;i<=q-1;i++) tag[i]+=x;
}
}
区间查询
题目要求 lower_bound来快速计算。还是一样,对于不完整的块,还是暴力求解。对于完整的块,直接对块内的元素二分查找即可。
具体代码如下:
int query(int l,int r,int x)
{
int p=in[l],q=in[r];
int res=0;
if(p==q)
{
for(int i=l;i<=r;i++) res+=(a[i]+tag[p]>=x);
return res;
}
for(int i=l;i<=R[p];i++) res+=(a[i]+tag[p]>=x);
for(int i=L[q];i<=r;i++) res+=(a[i]+tag[q]>=x);
for(int i=p+1;i<=q-1;i++)
res+=len-(lower_bound(v[i].begin(),v[i].end(),x-tag[i])-v[i].begin());
return res;
}