前缀和习题 :游戏之神,最大矩阵和,激光炸弹

学完前缀和,还是需要习题巩固一下的

目录

1.游戏之神

2.杭电1559-求二维数组最大矩阵和

3. acwing-99激光炸弹


1.游戏之神

逐句来看,给我n个非负整数,说明要有数组来存放他们,提出q个问题,即q个询问,。。。后面的就不用说了, 这道题是一个毫不拐弯的直白的一维数组前缀和问题。

需要注意的就是题中所限制的数据范围

我觉得这应该也是小白不太习惯的一个步骤(是我emm),之前的学习中感觉能写出来就不错了,都没考虑这种细节,但在比赛中这是必须要注意的。所以一些数据类型的取值范围还是记住比较好。附下表 

int 类型通常占用 4 个字节,能够表示的最大整数为 2^31 - 1(约为 2 * 10^9) 

题中要求n,q都是<=10^5,说明数组最多有10^5个元素,(最多有10^5次询问),所以开辟数组的大小应该至少是10^5。数组中的每个数字不超过10^9,所以存放原数的数组p[i]定义为int类型即可,我们还需要开辟一个新的数组来存放前缀和,最大会有 比如n非常大且每个元素也很大,导致前缀和很大的情况,所以前缀和数组q[i]应该定义为long long类型

#include<iostream>
using namespace std;
const int N=1e5+10;
int p[N];
long long q1[N];
int n,q;
int main()
{
    cin>>n>>q;
    for(int i=1;i<=n;i++)
    {
        cin>>p[i];
        q1[i]=q1[i-1]+p[i];
    }
    while(q--)
    {
        int f,t;
        cin>>f>>t;
        printf("%lld\n",q1[t]-q1[f-1]);//这里注意longlong类型的数据,格式化输出 应为%lld 否则会出错
    }
    return 0;
}

好啦,这道题就到这里啦,主要是数据范围的确定。


2.杭电1559-求二维数组最大矩阵和

首先明确题意,所要求的是x*y子矩阵和的最大值。在二维数组算法中,我们可以想到用一个数组存放原数据,另一个数组用来存放前缀和,给出(x1,y1)(x2,y2),即可求出该区域的所有元素的和。在这道题中,是把(x1,y1)(x2,y2)这样的明确的点的坐标所划定的范围换了一种说法,即x*y子矩阵的和,并且求其最大值。那么我们的思路转变为通过已知x*y来得到我们熟悉的坐标表示

用i,j来表示右下角坐标,也就是以(i,j)为右下角,那么它对应的围成x *y子矩阵的左上角坐标应为多少?可以通过下面这张图来理解

假设x*y的矩阵为2*2,右下角(i,j)为(4,3),那么它对应的左上角坐标应该是(3,2),由此可得左上角坐标的通式应该是(i-x+1,j-y+1),想一下,可以理解撒。

现在问题就很简单了,我们把题目转变为了我们熟悉的二维数组前缀和的形式。题目中要求求最大值,定义max,在通过if语句判断,遍历所有情况,不断更新最大值即可。

#include<iostream>
using namespace std;
const int N=1000;
int p[N][N],q[N][N];
int T,m,n,x,y;
int main()
{
    cin>>T;
    while(T--)
    {
        cin>>m>>n>>x>>y;
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                cin>>p[i][j];
                q[i][j]=q[i-1][j]+q[i][j-1]-q[i-1][j-1]+p[i][j];
            }
        }
        int max=0;
        for(int i=x;i<=m;i++)
        {
            for(int j=y;j<=n;j++)
            {
                if(q[i][j]-q[i-x][j]-q[i][j-y]+q[i-x][j-y]>max)//通式中的+1和本应该-1相互抵消
                {
                    max=q[i][j]-q[i-x][j]-q[i][j-y]+q[i-x][j-y];
                }
            }
        }
        cout<<max<<endl;
    }
    return 0;
}

那这道题先到这里咯。 


3. acwing-99激光炸弹

这道题感觉不太好懂的是题意(问就是我没看懂😂)。

题目中说地图,先想到了二维数组,有N个目标,N是我们给的炸弹个数,也是放炸弹的次数,后面给出的(x,y)是我们决定把炸弹放在那个位置, 放上去之后这个位置就具有了从0到+w的价值,当输入与上次相同的(x,y)坐标时,该点价值累加,对应题目中 不同目标可放在同一位置 ,可以想到在地图中初始每个位置都是0,经过输入坐标确定w的值使每个位置的价值不同,而题目要求的就是规定一个半径R,在R*R的矩阵中,区域内价值和最多的即为所求区域。

需要注意的是数据范围

这道题的时间限制是10秒,空间限制是162MB

题中给出x,y的最大值是5000,说明地图最大是5000*5000,也就是我们定义二维数组时,一个这样的二维数组所占空间为5000*5000*4/1024/1024=95MB(单位转换 *4 是因为一个int类型的数据占4个字节),如果像之前一样定义两个二维数组,一个用于存储原数,一个用于存储前缀和,那么在这道题中,所占空间范围为190MB远远大于题目限制。由于存储原数的数组只使用一次且其功能可以被前缀和数组通过自身更新实现(同理前面两道题也可以省去一个数组)

(这个空间大小的计算不可能每次都现场算一下,要记住一些常见的)

q[i][j] = q[i-1][j]+q[i][j-1]-q[i-1][j-1]+q[i][j];

这里是因为,在执行这行代码之前,q[i][j]本身就是有值的,可以看作一个数,这里是对q[i][j]重新赋值,使其表示前缀和。同理,当q[i][j]需要我们人为输入时也是可以直接这样更新数组,这样的好处是省去了开启数组存放原数据的空间。

所以只需定义一个二维数组。

另一个需要注意的范围是R的范围,是10^9远远大于地图大小,所以当输入的R值大于5000时直接取R=5000即可,因为无论多大都只能炸掉整个地图而已。

#include<iostream>
using namespace std;
const int N=5010;//地图最大范围是5000*5000的
int n,r;
int q[N][N];
int main()
{
    cin>>n>>r;
    //首先要完成的步骤是放炸弹 相当于完成将原数放入数组的步骤.
    r=min(5000,r);
    while(n--)
    {
        int x,y,w;
        cin>>x>>y>>w;
        q[x+1][y+1] += w;//+=实现不同目标在同一位置
//因为使用前缀和必须是从1开始的,但这道题里包含了0,所以我们必须把整个地图都向右下平移一下
    }
    //接下来就要实现计算前缀和的过程了
    for(int i=1;i<=5001;i++)
    //地图坐标是从(0,0)到(5000,5000)我这样写相当于把坐标拉到了从(1,1)到(5001,5001)
    {
        for(int j=1;j<=5001;j++)
        {
            q[i][j] = q[i-1][j]+q[i][j-1]-q[i-1][j-1]+q[i][j];
        }
    }
    //接下来求出所求区域和
    int max1=0;
    for(int i=r;i<=5001;i++)//这里定义从(r,r)开始,是将(i,j)视为矩阵右下角坐标向左上角拓展区域
    {
        for(int j=r;j<=5001;j++)
        {
            if(q[i][j]-q[i-r][j]-q[i][j-r]+q[i-r][j-r]>max1)
            {
                max1=q[i][j]-q[i-r][j]-q[i][j-r]+q[i-r][j-r];
            }
        }
    }
    cout<<max1<<endl;
    return 0;
}

好啦,这道题就到这里啦。 

23.6.30重新纠正了一下激光炸弹这道题的错误(解释写在注释里了),代码已经纠正过来了。时刻谨记 前缀和算法必须是从1开始,当题目里要(0,0)点也计算在内时,我们只能采取把整个坐标图都平移一下的方法。


这几道题做了好久,对题目的理解上感觉还是要多练,一些注意点要记住,加油!!

欢迎交流和建议哦

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值