降维技巧

前缀和

前缀和用于解决连续询问区间和,并且中途不插入新数的一系列问题。

一维前缀和

以下是前缀和的预处理和查询:

预处理:

for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];

查询 lr 的和:

sum[r]-sum[l-1];

可以发现,预处理的时间复杂度是 O(n),查询的时间复杂度是 O(1)

例题:

上面讲的是一维前缀和,其实二维前缀和也是同理。

二维前缀和

sumi,j 表示以 (1,1) 为左上角,(i,j) 为右下角的矩形内所有数字的和,那么有容斥原理,有如下关系:

sumi,j=sumi1,j+sumi,j1sumi1,j1+ai,j

照上式,可以完成预处理。

查询也有如下关系:

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];
}

同理,预处理 O(nm),查询 O(1)

例题:

异或前缀和

快速查询区间异或和。

预处理:

for(int i=1;i<=n;i++) sum[i]=(sum[i-1]^a[i]);

查询:

sum[r]^sum[l-1];

提一嘴树上前缀和,其实这种东西通常与树上差分一起,单独很少出现。

树上点权前缀和

sumi 表示根节点到 i 的前缀和,预处理跑一遍 dfs 即可,查询如下:

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];
}

差分

差分是前缀和的逆运算,即差分数组的前缀和就是原数组,差分数组可用于快速处理区间加/减,然后一起查询,如果要边加边查询,就要借助树状数组等数据结构。

一维差分

差分数组的定义如下:

对于数组 ab 为其差分数组:

b1=a1bi=aiai1(i>2)

例题:

关于二次差分,指在差分数组上再进行一次差分。可以处理在区间上加等差数列的问题。

由于等差数列由首项,项数,公差组成,不难发现,在一次差分数组上加上首项,在二次差分数组上加上公差,最后从下往上进行前缀和即可计算。

题目:三步必杀

二维差分

快速处理矩形加/减。

将左上角是 (x,y) 右下角是 (c,d) 的矩形加上 k.

b[x][y]+=k;
b[c+1][d+1]+=k;
b[c+1][y]-=k;
b[x][d+1]-=k;

单点修改也是同理。

例题:

异或差分

lr 同时异或上一个数字 x.

diff[l]^=x;
diff[r+1]^=x;

单点修改同理。

例题:

下面说说树上问题。

树上点权差分

x,y 的最短路径上,将所有点加上 k.

diff[x]+=k;
diff[y]+=k;
diff[lca(x,y)]-=k;
diff[fa[lca(x,y)]]-=k;

在查询时做一遍树上前缀和即可。

例题:

树上边权差分

同样,将边权下放至点,再差分。

xy 上的所有边加 k

diff[x]+=k;
diff[y]+=k;
diff[lca(x,y)]-=2*k;

例题:

双指针

双指针能解决的问题一定是能用两个 for 循环嵌套解决的,所以发现题目能用两个 for 循环解决的时,考虑双指针。

双指针维护一个前指针 l 和一个后指针 r,当题目满足 r 右移,l 要么不动要么向右移时,说明双指针时可行的,也称作符合单调性。

代码模板:

  • 越短越容易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;

单调队列

维护与 i 保持固定距离范围的最值,维护最大值用单调递减,维护最小值用单调递增。

如果可以用单调队列维护,一定要满足,“比你小还比你强,你就退役”的说法。对于maxixj,iyja ,显然是不能用一个单调队列维护的,因为 x,y 不止一个,所以距离 i 的范围不等,不能用单调队列维护,但是可以对于每一个 x,y ,开一个单调队列,这样就是可维护的。

通常用于 dp 优化。

代码模板:

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(符合条件) 更新答案;
}

题目:

单调栈

单调栈常用于求一个数左/右边第一个比他大/小的是谁。

可以用于维护每个点作为 maxmin 所占据的区间,从而处理区间内关于 min,max 的相关问题。

也可以处理子矩形问题,只需向左右找到比自己小的,便可计算出包含自己且高度是自己的矩形宽。

常见处理套路:对于左边求不包括等于的,对于右边求包括等于的,这样可以避免重复计算。

求左边还是右边,将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);
}
posted @   2017BeiJiang  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示