P2801 教主的魔法

题目通道\]([教主的魔法 - 洛谷](https://www.luogu.com.cn/problem/P2801 "教主的魔法 - 洛谷")) ### 摘要 分块,是一种**优雅**的暴力,它通过对数列分段,完成对数列一些区间操作和区间查询的操作,是一种根号算法。 这篇学习笔记\&题解是本**萌新** 在学习分块过程中的一些感悟,希望能够帮助分块零基础的同学学会**基础**分块。 *** ** * ** *** ### 0 说明 本文中,以下变量有特定的含义: * block⁡block:块的大小 * nn:被分块的数列的大小(长度) * LxLx:第 xx 号块的左边界 * RxRx:第 xx 号块的右边界 * tot⁡tot:块的数量 * belong⁡xbelongx:第 xx 号元素所属的块 在写作时,由于本萌新的失误,只好提前在这里令 \[l,r\]\[l,r\] 与 \[x,y\]\[x,y\] 等价。 *** ** * ** *** ### 1 建块 #### 1.1 建块需要完成的任务 在读入数据后,建块需要完成以下几个任务: * 确定块的大小 * 确定块的数量 * 标记每个块的左右边界 * 标记每个元素所属的块 * 对每个块的数据进行初始化 #### 1.2 确定块的大小 一般来说,我们习惯于令 block⁡=nblock=n​。 但是由于毒瘤良心命题人泛滥,block⁡=nblock=n​ 极其有可能被针对,在这种情况下,我们可以对块的大小适当作出一些调整,例如 n+1n​+1,n−1n​−1,nlg⁡(n)lg(n)n​​ 等。 一般这个工作只有一句话: block = (int)sqrt((double)n); #### 1.3 确定块的数量 在确定了块的大小后,块的数目就很容易确定了。 但是 nn 不一定是一个完全平方数,我们需要把最后几个无法凑足 block⁡block 个元素的再单独分一个块。 代码如下: tot = n / block; if(n % block) tot++; #### 1.4 标记每个块的左右边界 非常显然,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⋅block⁡Lx​=(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.5 标记每个元素所属的块 根据 1.4,我们很容易推出公式如下: belong⁡x=x−1block⁡+1belongx​=blockx−1​+1 代码如下: for(int i = 1; i <= n; i++) belong[i] = (i - 1) / block + 1; **重要:在使用分块过程中,一定要注意区分 tot⁡tot 和 nn。** tot⁡tot 是块的总数,nn 是原来元素的总数。 #### 1.6 对每个块的元素进行初始化 这项工作因题目不同而不同,如【教主的魔法】一题,就要对每个块的元素进行排序。 **因为排序会对原始数列作出改变,所以在本题中,应当先把数列复制一遍再进行分块** *** ** * ** *** ### 2 分块题常见的操作 修改: * 对数列 \[l,r\]\[l,r\] 内的每个数加上 kk * 对数列 \[l,r\]\[l,r\] 内的每个数减去 kk * etc. 查询: * 求数列 \[l,r\]\[l,r\] 内的所有数的和 * 求数列 \[l,r\]\[l,r\] 内的数有多少大于/小于/大于等于/小于等于 kk * etc. *** ** * ** *** ### 3 修改操作 考虑两种修改操作本质相同,第二种修改操作相当于第一种修改操作中 k=−k′k=−k′。 #### 3.1 暴力修改 考虑枚举区间 \[l,r\]\[l,r\] 之间所有数,直接对其实施修改,在修改的过程中维护每一个块的和/大小关系等。 但这不是我们考虑的东西 #### 3.2 考虑线段树思想 线段树一个重要思想:lazytag 考虑应用在分块中。在修改操作中,如果是整块,就不维护每个的具体信息,而是在这个块的 lazy⁡lazy 标记上加上 kk。对于没有整块修改的部分(即块 belong⁡xbelongx​ 和 belong⁡ybelongy​ 的修改部分),暴力修改。 这样的话,第 ii 个数据 aiai​ 的真正数据值为 ai+lazy⁡belong⁡iai​+lazybelongi​​。 如果询问涉及到排序,块 belong⁡xbelongx​ 和 belong⁡ybelongy​ 需要全部重新备份和排序,对于块 \[belong⁡x+1,belong⁡y−1\]\[belongx​+1,belongy​−1\] 的块,数的相对大小不会改变,所以可以不重新排序。 特别地,需要特判 belong⁡x=belong⁡ybelongx​=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; 不用特判最后一块的原因是:如果操作区间覆盖到的最后一块,也一定是作为 belong⁡ybelongy​ 处理掉了,剩下来的块长一定是 block⁡block。 *** ** * ** *** ### 4 查询操作 #### 4.1 查询元素和 对于块 belong⁡xbelongx​ 和 belong⁡ybelongy​,暴力枚举加和,注意加上其元素后还要加上 lazy⁡belong⁡ilazybelongi​​ 对于 \[belong⁡x+1,belong⁡y−1\]\[belongx​+1,belongy​−1\] 的块,直接 `ans=ans+sum[i]` 即可。 同样的,需要特判 belong⁡x=belong⁡ybelongx​=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.2 查询关系 与4.1类似,在块 belong⁡xbelongx​ 和 belong⁡ybelongy​,暴力枚举求答案; 对于 \[belong⁡x+1,belong⁡y−1\]\[belongx​+1,belongy​−1\] 的块,因为其是有序的,进行二分找到端点位置,然后加加减减求出块中有多少符合要求的元素即可。 本处代码见5. *** ** * ** *** ### 5 教主的魔法 在学习完分块后,我们可以发现,教主的魔法就是一道裸的分块题。 因此,完整代码如下: ```cpp #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<

相关推荐
lcg_magic9 小时前
图论系列(一):基础概念与术语解析
图论
127_127_1274 天前
2025 FJCPC 复建 VP
数据结构·图论·模拟·ad-hoc·分治·转化
wwer1425263635 天前
数学建模_图论
数学建模·图论
ysa0510305 天前
Dijkstra 算法#图论
数据结构·算法·图论
一只鱼^_5 天前
基础算法合集-图论
数据结构·算法·深度优先·图论·广度优先·宽度优先·图搜索算法
ysa0510306 天前
图论基础算法入门笔记
数据结构·c++·笔记·算法·图论
闻缺陷则喜何志丹18 天前
【并集查找】P10729 [NOISG 2023 Qualification] Dolls|普及+
c++·算法·图论·洛谷·并集查找
CodeWithMe18 天前
【Algorithm】图论入门
c++·图论
东方芷兰20 天前
Leetcode 刷题记录 13 —— 图论
算法·leetcode·图论
蒙奇D索大21 天前
【数据结构】图论实战:DAG空间压缩术——42%存储优化实战解析
数据结构·笔记·学习·考研·图论·改行学it