分块学习笔记
分块
通过适当的划分,预处理并且保存下来,用空间换取时间,达到空间平衡。和线段树/树状数组相比更加接近朴素做法,但是更加通用、直观、容易实现(抄自算阶)。
模板(数列分块入门1)
分块长度为\(\sqrt{n}\),大块打标记,小块直接修改,查询值为标记 \(+\) 原值。
预处理
for(int i=1;i<=s;i++)lf[i]=rt[i-1]+1,rt[i]=i*s;
if(rt[s]<n)s++,lf[s]=rt[s-1]+1,rt[s]=n;
for(int i=1;i<=s;i++)
for(int j=lf[i];j<=rt[i];j++)pos[j]=i;
查询&修改
long long query(int x)
{
return a[x]+tg[pos[x]];
}
void upd(int l,int r,long long k)
{
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)a[i]+=k;
}
else
{
for(int i=l;i<=rt[pos[l]];i++)a[i]+=k;
for(int i=pos[l]+1;i<=pos[r]-1;i++)tg[i]+=k;
for(int i=lf[pos[r]];i<=r;i++)a[i]+=k;
}
}
树上分块
dfs时维护一个栈,离开每个点时压栈,自下向上合并,如果某棵子树深搜后栈内元素数量 \(\ge B\)就把栈内元素放在一个块里。但是
如果某棵子树深搜之后大小达不到 \(B\) ,但是在下一棵子树更靠下的位置超过了 \(B\) ,就会导致分块不联通,所以每次进入递归时还需要维护一个弹栈上限。
int top=0,st[N];//维护已经完成遍历的点
int cnt=0,rt[N],pos[N];//每个省的省会和各点在哪个省中
void dfs(int x,int fa=-1)
{
int las=top;//弹栈上限
for(int i=lk[x];i;i=e[i].nxt)
{
int y=e[i].y;
if(y==fa)continue;
dfs(y,x);
if(top-las>=b)//子树可以成为一个省
{
rt[++cnt]=x;
while(top>las)pos[st[top--]]=cnt;
}
}
st[++top]=x;//压栈
}
题目
数列分块入门2
预处理把每个大块放进vector里排一下序,修改同上,查询时lower_bound一下。数列分块入门3
同上,查询时返回下标对应值。
数列分块入门4
预处理每块的和,修改时大块标记小块暴力,查询时大块直接加小块暴力。
数列分块入门5
看起来很像势能线段树那题,所以可以用相似的做法。如果一个块内全部为 \(0/1\) ,打上标记,以后修改到此处就直接跳过了。
数列分块入门6
vector维护块内元素,直接插入,查询时大块跳过,小块暴力。因为数据是随机生成的,所以所有的插入操作不会让原先分的块变得太丑陋。但是如果不是随机数据的话就会破坏我们的复杂度,因此考虑重新分块。
数列分块入门7
和线段树2就很像。但是更新标记的时候只能直接把大块标记传递到单点上。
数列分块入门8
似乎无法预处理,但是注意到每次修改之后会有一片相同的值,所以小块暴力修改大块打标记。注意如果破坏大块修改小块需要传递并清空标记。
作诗
发现区间内元素出现次数很难用线段树维护,考虑分块。大块预处理答案和前缀和,小块暴力。
易错
- 注意块长算出 \(0\) 的情况。
- 不要忘记
pos数组赋初值。(?
浙公网安备 33010602011771号