【Acwing蓝桥杯笔记】前缀和&子矩阵的和&激光炸弹&k倍区间

前缀和

当我们想去快速求出来静态数组的某一个区间内所有数的和的时候,我们就可以用到前缀和的思想进行求解。

image-20220403221434621

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

const int N=100010;

int n,m;
int a[N];//原数组
int s[N];//前缀和数组 全局数组初值默认是0

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        s[i]=s[i-1]+a[i];
    }
    
    while(m--)//询问m次
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",s[r]-s[l-1]);
    }
    return 0;
}

①子矩阵的和

image-20220403230527679

  • 如何从原矩阵中得到前缀和矩阵(容斥原理)

S(xy)=S(x-1.y)+S(x.y-1)-S(x-1.y-1)+a(x.y)

文字表达:前缀和矩阵等于基准格的上面所有的格子之和加上基准格左边所有格子之和再减去被加了两次的重合部分最后加上基准格本身。

  • 如何利用前缀和矩阵,计算某一个子矩阵的和?(容斥原理)

a(x.y)=S(xy)-S(x-1.y)-S(x.y-1)+S(x-1.y-1)

其实是同一个公式。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=1010;

int n,m,q;
int a[N][N],s[N][N];

int main()
{
    scanf("%d%d%d",&n,&m,&q);
    
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
      {
          scanf("%d",&a[i][j]);
          s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
      }
      while(q--)
      {
          int x1,y1,x2,y2;
          scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
          printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
      }
      return 0;
}
②激光炸弹

image-20220405155217006

R最大值为5001

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=5010;

int n,m;//长、宽
int s[N][N];//同时用于记录位置和前缀和数组(记录价值)

int main()
{
    int cnt,R;//点的个数和边长
    cin>>cnt>>R;
    R=min(5001,R);//若越界则取可取得最大值
    n=m=5001;
    while(cnt--)
    {
        int x,y,w;
        cin>>x>>y>>w;//传入坐标和价值
        x++;y++;//防止数据越界
        s[x][y]=s[x][y]+w;
    }
    //预处理前缀和
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
      s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
      
    int res=0;
    //枚举所有边长是R的矩形 枚举(i,j)为右下角坐标
    for(int i=R;i<=n;i++)
      for(int j=R;j<=m;j++)
          res=max(res,s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R]);//从中选出最大的
          
          cout<<res<<endl;
          
          return 0;
    
}
③k倍区间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hn5n330V-1649381659749)(C:/Users/21408/Desktop/image-20220405234457217.png)]

连续数列从1~n

考虑进行枚举 枚举右端点和左端点 将所有的子区间都枚举到 之后判断此区间是否为k倍区间(再进行一个暴力搜索)

求一下区间和 check一下是不是K的倍数(取模为0)

(肯定会超时Orz……)

那就浅优化一下吧。求和可以用前缀和!!

int res =0;
for(R=1;R<=n;R++)
    for(L=1;L<=R;L++)
    {
        int sum=s[R]-s[L-1]
            if(sum%k==0) res++;
    }

还能怎么优化呢?

把问题

当R固定时,在1~R之间找到有多少个L满足(S[R]-S[L-1])%k==0

转换为:

当R固定时,在0~R-1之间找到有多少个L满足(S[R]-S[L])%k==0

即:有多少个S[L]与S[R]模k的余数相同(存余数)

int res=0;
for(R=1;R<=n;R++)
{
res+=cnt[R%k];
cnt[R%k]++;//余数是i的数有多少个
}

完整代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;//防止越界

const int N=100010;

int n,k;
LL s[N],cnt[N];

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&s[i]);//读入数组的数
        s[i]+=s[i-1];//同时计算前缀和
    }
    LL res=0;
    cnt[0]=1;
    for(int i=1;i<=n;i++)
    {
        res+=cnt[s[i]%k];//k倍区间的数量
        cnt[s[i]%k]++;//余数是si%k的个数
    }
    printf("%lld\n",res);
    return 0;
}

扩展调试技巧:

void f()
{
  exit(0)
}

把f()放到程序中的各个位置可以去判断到底是哪里的Segmentation Fault错误

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AKA山风点火

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值