[题目通道](教主的魔法 - 洛谷)
分块,是一种优雅的暴力,它通过对数列分段,完成对数列一些区间操作和区间查询的操作,是一种根号算法。
这篇学习笔记&题解是本萌新在学习分块过程中的一些感悟,希望能够帮助分块零基础的同学学会基础分块。
本文中,以下变量有特定的含义:
在写作时,由于本萌新的失误,只好提前在这里令 [l,r][l,r] 与 [x,y][x,y] 等价。
在读入数据后,建块需要完成以下几个任务:
一般来说,我们习惯于令 block=nblock=n。
但是由于毒瘤良心命题人泛滥,block=nblock=n 极其有可能被针对,在这种情况下,我们可以对块的大小适当作出一些调整,例如 n+1n+1,n−1n−1,nlg(n)lg(n)n 等。
一般这个工作只有一句话:
block = (int)sqrt((double)n);
在确定了块的大小后,块的数目就很容易确定了。
但是 nn 不一定是一个完全平方数,我们需要把最后几个无法凑足 blockblock 个元素的再单独分一个块。
代码如下:
tot = n / block;
if(n % block) tot++;
非常显然,L1=1,R1=block,L2=block+1,R2=2×block,⋯L1=1,R1=block,L2=block+1,R2=2×block,⋯
从而可以得出结论:
Lx=(x−1)⋅block+1,Rx=x⋅blockLx=(x−1)⋅block+1,Rx=x⋅block
特别地,Rtot=nRtot=n
代码:
for(int i = 1; i <= tot; i++){
L[i] = (i - 1) * block + 1;
R[i] = i * block;
}
R[tot] = n;
根据 1.4,我们很容易推出公式如下:
belongx=x−1block+1belongx=blockx−1+1
代码如下:
for(int i = 1; i <= n; i++)
belong[i] = (i - 1) / block + 1;
重要:在使用分块过程中,一定要注意区分 tottot 和 nn。 tottot 是块的总数,nn 是原来元素的总数。
这项工作因题目不同而不同,如【教主的魔法】一题,就要对每个块的元素进行排序。
因为排序会对原始数列作出改变,所以在本题中,应当先把数列复制一遍再进行分块
修改:
查询:
考虑两种修改操作本质相同,第二种修改操作相当于第一种修改操作中 k=−k′k=−k′。
考虑枚举区间 [l,r][l,r] 之间所有数,直接对其实施修改,在修改的过程中维护每一个块的和/大小关系等。
但这不是我们考虑的东西
线段树一个重要思想:lazytag
考虑应用在分块中。在修改操作中,如果是整块,就不维护每个的具体信息,而是在这个块的 lazylazy 标记上加上 kk。对于没有整块修改的部分(即块 belongxbelongx 和 belongybelongy 的修改部分),暴力修改。
这样的话,第 ii 个数据 aiai 的真正数据值为 ai+lazybelongiai+lazybelongi。
如果询问涉及到排序,块 belongxbelongx 和 belongybelongy 需要全部重新备份和排序,对于块 [belongx+1,belongy−1][belongx+1,belongy−1] 的块,数的相对大小不会改变,所以可以不重新排序。
特别地,需要特判 belongx=belongybelongx=belongy 的情况。
代码:
void change(){
if(belong[x] == belong[y]){
for(int i = x; i <= y; i++){
a[i] += k;
sum[belong[x]] += k;
}
return;
}
for(int i = x; i <= R[belong[x]]; i++){
a[i] += k;sum[belong[x]] += k;
}
for(int i = L[belong[y]]; i <= y; i++){
a[i] += k; sum[belong[y]] += k;
}
for(int i = belong[x] + 1; i <= belong[y] - 1; i++){
lazy[i] += k;
sum[i] += blo * k;
}
}
对以下这句代码作出特别解释:
sum[i] += blo * k;
不用特判最后一块的原因是:如果操作区间覆盖到的最后一块,也一定是作为 belongybelongy 处理掉了,剩下来的块长一定是 blockblock。
对于块 belongxbelongx 和 belongybelongy,暴力枚举加和,注意加上其元素后还要加上 lazybelongilazybelongi
对于 [belongx+1,belongy−1][belongx+1,belongy−1] 的块,直接 ans=ans+sum[i]
即可。
同样的,需要特判 belongx=belongybelongx=belongy
代码:
int query_sum(){
int ans = 0;
if(belong[x] == belong[y]){
for(int i = x; i <= y; i++){
ans += a[i] + lazy[belong[x]];
}
return ans;
}
for(int i = x; i <= R[belong[x]]; i++){
ans += a[i] + lazy[belong[x]];
}
for(int i = L[belong[x]]; i <= y; i++){
ans += a[i] + lazy[belong[y]];
}
for(int i = belong[x] + 1; i <= belong[y] - 1; i++){
ans += sum[i];
}
return ans;
}
与4.1类似,在块 belongxbelongx 和 belongybelongy,暴力枚举求答案;
对于 [belongx+1,belongy−1][belongx+1,belongy−1] 的块,因为其是有序的,进行二分找到端点位置,然后加加减减求出块中有多少符合要求的元素即可。
本处代码见5.
在学习完分块后,我们可以发现,教主的魔法就是一道裸的分块题。
因此,完整代码如下:
#include
#include
using namespace std;
int m,n,t,pos[1251000];
int s[2151000],flag[1251000];
vectorv[550000];
void reset(int x) {
v[pos[x]].clear();
for(int i=(pos[x]-1)*m+1; i<=min(pos[x]*m,n); i++)
v[pos[x]].push_back(s[i]);
sort(v[pos[x]].begin(),v[pos[x]].end());
}
void change(int a,int b,int c) {
for(int i=a; i<=min(pos[a]*m,b); i++)
s[i]+=c;
reset(a);
if(pos[a]!=pos[b]) {
for(int i=(pos[b]-1)*m+1; i<=b; i++)
s[i]+=c;
reset(b);
}
for(int i=pos[a]+1; i<=pos[b]-1; i++)
flag[i]+=c;
}
int query(int l,int r,int c) {
int ans=0;
for(int i=l; i<=min(pos[l]*m,r); i++)
if(s[i]+flag[pos[l]]>x;
scanf("%d%d%d",&a,&b,&c);
if (x=='M') {
change(a,b,c);
} else if (x=='A') {
cout<