week 2 总结
1. 涉及知识点
- 分块,莫队
- 矩阵乘法
2. 分块(思想)
大则分,分则快
2.1 什么是分块思想?
将连续的一段分成若干块,在块内维护信息,对于区间查询,暴力查询两端,对于整块直接使用记录的信息,对于块长为 $s$ 总长为 $n$ 的数组,其一次区间查询/修改操作约为 $O(2\times s+\frac{n}{s})$,当 $s=\sqrt{n}$ 时,一次操作复杂度为 $O(2\times\sqrt{n}+\sqrt{n})=O(\sqrt{n})$ 。
虽然比线段树每次操作 $O(\log n)$ 稍为逊色,但是代码更加好写。
在一般情况下,$n\le 10^{4}$ 左右使用分块能通过测试。
2.2 分块中的概念
下面以 $s$ 作为块长。
- 块的开头:对于第 $i$ 块,其开头为 $(i - 1) \times s + 1$。
- 块的结尾:对于第 $i$ 块,其结尾为 $i\times s$。
- 所在的块:对于下标为 $1,2,...,n$ 的的一串数,对于第 $i$ 个数所属的块为 $(i-1)/s+1$。
- Lazy:同样地,对每个快也可以打上 Lazy。
2.3 分块实战
例题 2.3.1 区间极大值2
有一个长度为n的整数数列 $ a $ 。
现在有 $ m $ 个操作,操作的格式有两种:
1 x y
,表示修改,将数列第 $ x $ 个数 $ a[x] $ 改为 $ a[x]+y $ ;
2 x y
, 表示询问,询问第 $ x $ 个数到第 $ y $ 个数间,最大的一个数是多少。
分析:
线段树经典入门题,但是现在我们使用分块来写。
分块:
1.把长度为N的区间分成若干区间块,每块长度为 $S$,共 $N/S$ 块(最后一块除外);
2.其中第i块表示的区间范围是 $[(i-1)*S+1 , i*S]$;
3.将每一块中最大的数字记录下来,其中第 $i$ 块记录在 $block_i$ 中;
代码实现:
s=int(sqrt(n))+1;
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
b[(i-1)/s+1].max=max(b[(i-1)/s+1].max,a[i]);
}
修改:
- $A_X=A_X+Y$;
2.找出 $ X$ 所在块的块号 $i=(X-1)/S+1$,暴力枚举块 $i$ 中的每个数$k$,若$a_k>block_i$ 则更新 $block_i$。
代码实现:
int k=(x-1)/s+1,L=(k-1)*s+1,R=k*s;
b[k].max=-inf,a[x]+=y;
for(int i=L; i<=R; i++)b[k].max=max(b[k].max,a[i]);
查询:
1.计算X所在块号 $i=(X-1)/S+1$, Y所在块号 $j=(Y-1)/S+1$;
2.若 $ i==j $ ,说明区间 $ [X,Y] $ 在同一块中,直接暴力枚举查询最大值
3.若 $i\not=j$,区间 $ [X,Y] $ 的左右两端可能覆盖了某块的一部分,两端直接暴力枚举。中间覆盖的若干整块直接讨论 $ block $ 值。
代码实现:
int i=(x-1)/s+1,j=(y-1)/s+1,ans=-inf;
if(i==j) for(int k=x; k<=y; k++) ans=max(ans,a[k]+b[i].lazy);
else {
for(int k=x; k<=i*s; k++)ans=max(ans,a[k]+b[i].lazy);
for(int k=(j-1)*s+1; k<=y; k++)ans=max(ans,a[k]+b[j].lazy);
for(int k=i+1; k<j; k++)ans=max(ans,b[k].max+b[k].lazy);
}
3. 莫$^{[1]}$队
$[1]$:莫涛。
3.1 思想
1.分块,将数列分成 $ N/S $ 块 ,块长一般为 $ \sqrt{N} $ (或 $N^{\frac{2}{3}}$)
2.将询问离线处理:
(1).将询问排序,第一关键字为询问左端点所在块号,第二关键字为询问右端点;
(2).依次处理每个询问,当求得 $ [L,R] $ 区间的解后,在 $ O(1) $ 时间就能得到区间 $ [L-1,R],[L,R-1],[L+1,R],[L,R+1] $ 的解;
3.分块仅仅是用于排序操作,只存在与思维中;
4.左指针L始终往右走,时间复杂度为 $ O(N) $
右指针在同一块中只会往右走,复杂度为 $ O(\sqrt{N}) $
右指针移动一次最坏为 $ O(N) $ ,可能操作 $ \sqrt{N} $ 次,时间复杂度为 $ O(N\sqrt{N}) $
3.2 实战
将询问排序,排序的第一关键字为询问左端点所在块号,第二关键字为询问的右端点
$ Cnt[i] $ 记录当前讨论的区间 $ [L,R] $ 中,数字 $ i $ 出现的次数
若当前询问为区间 $ [x,y] $ ,先把 $ R $ 移动到 $ y $ 位置,再把 $ L $ 移动到 $ x $ 位置, 统计数字出现次数的变化。
代码实现 :
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int p[maxn],Cnt[maxn],Color[maxn],Ans[maxn],n,m,Tot,s;
struct node{
int l,r,id;
}a[maxn];
bool cmp(node a,node b) {
if(p[a.l]==p[b.l])return a.r<b.r;
return p[a.l]<p[b.l];
}
void Add(int p) {
int k =Color[p];
Cnt[k]++;
if(Cnt[k]==1)Tot++;
}
void Del(int p) {
int k =Color[p];
Cnt[k]--;
if(Cnt[k]==0)Tot--;
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&Color[i]);
}
s=sqrt(n);
for(int i=1,j=1; i<=n; i++) {
p[i]=j;
if(i%s==0)j++;
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&a[i].l,&a[i].r);
a[i].id=i;
}
sort(a+1,a+m+1,cmp);
int L=1,R=0;
for(int i=1; i<=m; i++) {
while(R<a[i].r)Add(R+1),R++;
while(R>a[i].r)Del(R),R--;
while(L<a[i].l)Del(L),L++;
while(L>a[i].l)Add(L-1),L--;
Ans[a[i].id]=Tot;
}
for(int i=1;i<=m;i++)printf("%d\n",Ans[i]);
}
4. 矩阵乘法
法则:
$$ \begin{bmatrix} a & b \\ c & d \end{bmatrix} \times \begin{bmatrix} e & f \\ g & h \end{bmatrix} = \begin{bmatrix} a\times e+b\times g & a\times f+b\times h \\ c\times e+d\times g & c\times f+d\times h \end{bmatrix} $$
代码实现:
matrix operator * (const matrix& a, const matrix& b) {
matrix c;
memset(c.m, 0, sizeof(c.m));
int r;
c.x=a.x,c.y=b.y;
for(int i=0; i<a.x; i++)
for(int k = 0; k<a.y; k++) {
r=a.m[i][k];
for(int j=0; j<b.y; j++) {
c.m[i][j] += r* b.m[k][j],c.m[i][j]%=mod;
}
}
return c;
}
矩阵快速幂
跟普通的快速幂几乎一样。
代码实现:
matrix qp(matrix a, int n) {
matrix ans;
ans.x=a.x,ans.y=a.y;
memset(ans.m,0,sizeof(ans.m));
for(int i=0; i<maxn; i++) ans.m[i][i] = 1;
while(n) {
if(n&1) ans = ans * a;
a = a * a;
n>>=1;
}
return ans;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现