降维技巧
前缀和
前缀和用于解决连续询问区间和,并且中途不插入新数的一系列问题。
一维前缀和
以下是前缀和的预处理和查询:
预处理:
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
查询 的和:
sum[r]-sum[l-1];
可以发现,预处理的时间复杂度是 ,查询的时间复杂度是 。
例题:
- [NOIP2011 提高组] 聪明的质监员
- 观察到式子中有 的形式,在 中使用前缀和先预处理,就可以 ,总时间复杂度
- 最大子段和
- 最大加权矩形
- 将二维压缩成一维,即可按照最大子段和的方式做。
- [USACO11MAR] Brownie Slicing G
上面讲的是一维前缀和,其实二维前缀和也是同理。
二维前缀和
设 表示以 为左上角, 为右下角的矩形内所有数字的和,那么有容斥原理,有如下关系:
照上式,可以完成预处理。
查询也有如下关系:
int query(int x,int y,int c,int d) {//左上角(x,y),右下角(c,d) return sum[c][d]-sum[x-1][d]-sum[c][y-1]+sum[x-1][y-1]; }
同理,预处理 ,查询 。
例题:
- 领地选择
- 板子题。
异或前缀和
快速查询区间异或和。
预处理:
for(int i=1;i<=n;i++) sum[i]=(sum[i-1]^a[i]);
查询:
sum[r]^sum[l-1];
提一嘴树上前缀和,其实这种东西通常与树上差分一起,单独很少出现。
树上点权前缀和
记 表示根节点到 的前缀和,预处理跑一遍 即可,查询如下:
int query(int x,int y) { return sum[x]+sum[y]-sum[lca]-sum[fa[lca]]; }
树上边权前缀和
首先将边权下放,除了查询其他同上。查询如下:
int query(int x,int y) { return sum[x]+sum[y]-2*sum[lca]; }
差分
差分是前缀和的逆运算,即差分数组的前缀和就是原数组,差分数组可用于快速处理区间加/减,然后一起查询,如果要边加边查询,就要借助树状数组等数据结构。
一维差分
差分数组的定义如下:
对于数组 , 为其差分数组:
例题:
- [NOIP2012 提高组] 借教室
- [USACO07MAR] Face The Right Way G
- 需要将 转化为奇偶,便于维护。
关于二次差分,指在差分数组上再进行一次差分。可以处理在区间上加等差数列的问题。
由于等差数列由首项,项数,公差组成,不难发现,在一次差分数组上加上首项,在二次差分数组上加上公差,最后从下往上进行前缀和即可计算。
题目:三步必杀
二维差分
快速处理矩形加/减。
将左上角是 右下角是 的矩形加上 .
b[x][y]+=k; b[c+1][d+1]+=k; b[c+1][y]-=k; b[x][d+1]-=k;
单点修改也是同理。
例题:
异或差分
将 同时异或上一个数字 .
diff[l]^=x; diff[r+1]^=x;
单点修改同理。
例题:
- [USACO07MAR] Face The Right Way G
- 奶牛转向,即可看做区间异或 。
下面说说树上问题。
树上点权差分
在 的最短路径上,将所有点加上 .
diff[x]+=k; diff[y]+=k; diff[lca(x,y)]-=k; diff[fa[lca(x,y)]]-=k;
在查询时做一遍树上前缀和即可。
例题:
树上边权差分
同样,将边权下放至点,再差分。
将 上的所有边加 。
diff[x]+=k; diff[y]+=k; diff[lca(x,y)]-=2*k;
例题:
双指针
双指针能解决的问题一定是能用两个 循环嵌套解决的,所以发现题目能用两个 循环解决的时,考虑双指针。
双指针维护一个前指针 和一个后指针 ,当题目满足 右移, 要么不动要么向右移时,说明双指针时可行的,也称作符合单调性。
代码模板:
-
越短越容易OK。
int l=1;//左端点 for(int i=1;i<=n;i++) {//右端点 加上当前i的影响 while(l<=i&&不满足条件) { 将之前的l影响消除 l++; } if(满足条件) 更新答案 } 在 唯一的雪花 Unique Snowflakes 中,便使用了上面的代码模板。
int l=1,ans=0; for(int i=1;i<=n;i++) { b[a[i]]++; while(l+1<=i&&b[a[i]]>1) { b[a[l]]--; l++; } ans=max(ans,i-l+1); } -
越长越容易OK。
int r=0;//右端点 for(int i=1;i<=n;i++) {//左端点 if(i!=1) 减去i-1的影响 while(r+1<=n&&不符合要求) { r++; 加上r的贡献 } if(满足要求) 更新答案 } 在[NOIP2011 提高组] 选择客栈 中,使用上述模板。
int cnt=0; LL ans=0; for(int i=1,j=0;i<=n;i++) { if(i!=1&&b[i-1]<=p) cnt--; while(j+1<=n&&(cnt==0||i==j)) { j++; if(b[j]<=p) cnt++; } if(i!=j&&cnt!=0) ans+=(LL)s[j][a[i]]; } cout<<ans;
单调队列
维护与 保持固定距离范围的最值,维护最大值用单调递减,维护最小值用单调递增。
如果可以用单调队列维护,一定要满足,“比你小还比你强,你就退役”的说法。对于 ,显然是不能用一个单调队列维护的,因为 不止一个,所以距离 的范围不等,不能用单调队列维护,但是可以对于每一个 ,开一个单调队列,这样就是可维护的。
通常用于 优化。
代码模板:
int l=1,r=0; int n,m; for(int i=1;i<=n;i++) { while(l<=r&&i-q[l]+1>m) l++; //将左端点不符合距离范围的去掉 while(l<=r&&calc(i)>calc(q[r])) r--; //将末尾不符合单调性的去掉 //单增还是单减,取决于上一句代码的 "<" 还是 ">" q[++r]=i; if(符合条件) 更新答案; }
题目:
单调栈
单调栈常用于求一个数左/右边第一个比他大/小的是谁。
可以用于维护每个点作为 或 所占据的区间,从而处理区间内关于 的相关问题。
也可以处理子矩形问题,只需向左右找到比自己小的,便可计算出包含自己且高度是自己的矩形宽。
常见处理套路:对于左边求不包括等于的,对于右边求包括等于的,这样可以避免重复计算。
求左边还是右边,将for循环倒序即可。若求大于自己,维护单减序列;若求小于自己,维护单增序列。
代码模板(下面是求右边比自己大的下标):
st.push(1); for(int i=2;i<=n;i++) { while(st.size()&&a[i]>a[st.top()]) { b[st.top()]=i; st.pop(); } st.push(i); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效