C++算法之二分与前缀和(2)

1.AcWing 795.前缀和

分析思路

由数列的前n项和可知,s[i]=a[i]+s[i-1]如果我们要计算[l,r]的和,可用公式s[r]-s[l-1]

代码实现
#include<iostream>

using namespace std;

const int N=100010;

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

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

2.AcWing 796.子矩阵的和

分析思路

①如何计算前缀和矩阵(容斥原理)

s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j]

②如何利用前缀和矩阵,计算某一个子矩阵的和?

sum=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]

代码实现
#include<cstdio>
#include<algorithm>

using namespace std;
const int N=1010;
int a[N][N],s[N][N];
int n,m,q;

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][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j];
        }
    }
    while(q--)
    {
        int x1,y1,x2,y2;
        int sum=0;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        sum=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
        printf("%d\n",sum);
    }
    return 0;
}

3.AcWing 99.激光炸弹

分析思路

此题和上面一题不一样的是,原来(x,y)为格子,而这里为点,所以在枚举长度为R的正方行的时候公式将改成s[i][j]-s[i-R][j]-s[i][j - R]+s[i-R][j-R](以(i,j)为右下角坐标开始枚举长度为R的正方形)

代码实现
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 5010;

int max_x, max_y;
int s[N][N];

int main()
{
    int cnt,R;
    cin>>cnt>>R;
    R=min(5001,R);//爆炸范围超过5001时覆盖了所有目标范围,因此没必要考虑超出范围。
    max_x = max_y = R;// 最小的爆炸计算范围是在一个最小爆炸范围之内计算
    
    while(cnt--)
    {
        int x,y,w;
        cin>>x>>y>>w;
        x++,y++;//边界问题都至少从1开始
        max_x=max(max_x,x),max_y=max(max_y,y);
        s[x][y]+=w;//还解决了重复出现的情况
    }
    
    //预处理前缀和
    for(int i=1;i<=max_x;i++)
        for(int j=1;j<=max_y;j++)
            s[i][j]+=s[i][j-1]+s[i-1][j]-s[i-1][j-1];
            
    int res=0;
    // 枚举所有边长是R的矩形,枚举(i, j)为右下角
    for(int i=R;i<=max_x;i++)
        for(int j=R;j<=max_y;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;
}

4.AcWing 1230.K倍区间

分析思路

暴力枚举会超时所以需要优化

线性同余定理:首先要知道一个定理, r % k 的余数 与 l % k 的余数相等, 那么( r - l) % k == 0

首先我们是从左依次往右, 判断 r 是否与 l(1 ~ r - 1) 的余数相等(不相等res+0), 如果相等, 则就要加上, 关键在于for循环里面的顺序, 首先要先加上cnt[a[i] % k ]的数量, 也就是除自己以外, 前面和自己余数相同的个数, 然后再进行cnt[a[i] % k] ++, 把自己也算进去, 这么依次递推。 

对于cnt[0] = 1的理解:
对于余数非0的数而言,他的结果一定是从0加到总数减1,对于余数是0本身的数,他自身就可以作为一个结果,因此要初始化为1,不需要等下一个区间同余出现再加1!

代码实现
#include<iostream>

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 count=0;
   cnt[0]=1;//不需要下一个区间出现再加1,本身就能直接加1
   for(int i=1;i<=n;i++)
   {
       count+=cnt[s[i]%k];
       cnt[s[i]%k]++;
   }
   
    cout<<count<<endl;
    return 0;
}

  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值