前缀和刷题

一维前缀和

  1. 定义:原数组 a 1 、 a 2 、 a 3 . . . a_1、a_2、a_3... a1a2a3... 下标从1开始

    ​ 前缀和 S i = a 1 + a 2 + . . . + a i S_i=a_1+a_2+...+a_i Si=a1+a2+...+ai S 0 = 0 S_0=0 S0=0

  2. 怎么求Si

    for(i=1;i<=n;i++)
        S[i]=S[i-1]+a[i];
    
  3. 作用:求原数组中一段数的和

    求[l,r]求这段区间中数的和,Sr — Sl-1

二维前缀和

  1. 定义:S[i, j] = 第 i 行 j 列格子左上部分所有元素的和

  2. 怎么求Si,j

    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            S[i,j]=S[i-1,j]+S[i,j-1]-S[i-1,j-1]+a[i,j];
    
  3. 作用:以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
    S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

领地选择

题目链接点这

题目描述
作为在虚拟世界里统帅千军万马的领袖,小 Z 认为天时、地利、人和三者是缺一不可的,所以,谨慎地选择首都的位置对于小 Z 来说是非常重要的。首都被认为是一个占地 C×C 的正方形。小 Z 希望你寻找到一个合适的位置,使得首都所占领的位置的土地价值和最高。
输入格式
第一行三个整数 N,M,C,表示地图的宽和长以及首都的边长。接下来 N 行每行 M 个整数,表示了地图上每个地块的价值。价值可能为负数。
输出格式
一行两个整数 X,Y,表示首都左上角的坐标。

题目分析
显然,这是一个二维前缀和问题,我们依次输入的N行M列数据就是原数组中每一个点的价值。而我们要求在 C×C 的面积里面最大的前缀和。
首先,我们求出利用前缀和公式求出Si,j

scanf("%d%d%d",&N, &M, &C);
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];//求前缀和
    }
}

然后,求从左上角的坐标(i,j)到右下角的坐标(i+C-1,j+C-1),面积为 C×C 的前缀和,注意i是从1到N-C+1,j是从1到M-C+1

 for(int i=1;i<=N-C+1;i++)
    {
        for(int j=1;j<=M-C+1;j++)
        {
            sum=S[i+C-1][j+C-1]-S[i-1][j+C-1]-S[i+C-1][j-1]+S[i-1][j-1];//求领土价值
        }
    }

最后再比较sum和Max,更新Max,得到它的坐标即可。

我的答案如下

#include <iostream>
#include <stdio.h>

using namespace std;

const int Z=1010;
int a[Z][Z], S[Z][Z], sum, Max;
int N, M, C, x, y;

int main()
{
    scanf("%d%d%d",&N, &M, &C);
    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];//求前缀和
        }
    }
    for(int i=1;i<=N-C+1;i++)
    {
        for(int j=1;j<=M-C+1;j++)
        {
            sum=S[i+C-1][j+C-1]-S[i-1][j+C-1]-S[i+C-1][j-1]+S[i-1][j-1];//求领土价值
            if(sum>Max)
            {
                Max=sum;
                x=i;
                y=j;
            }
        }
    }
    cout << x << " " << y ;
    return 0;
}

连续自然数求和

题目链接:点这

题目描述:
对一个给定的自然数MM,求出所有的连续的自然数段,这些连续的自然数段中的全部数之和为MM。
例子:1998+1999+2000+2001+2002 = 10000,所以从1998到2002的一个自然数段为M=10000的一个解。
输入格式:
包含一个整数的单独一行给出M的值(10≤M≤2,000,000)。
输出格式:
每行两个自然数,给出一个满足条件的连续自然数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。

题目分析:
显然,这是一个一维前缀和的题目,先利用 S[i]=S[i-1]+i 求出S[i],然后求出[l,r]这段区间元素的前缀和,如果等于M,直接输出此时的l和r。

我的答案如下

#include <iostream>
#include <stdio.h>

using namespace std;

const int N=2e6+10;
int S[N], sum, M;

int main()
{
    scanf("%d", &M);
    for(int i=1;i<=M;i++)
        S[i]=S[i-1]+i;
    for(int i=1;i<M;i++)
    {
        for(int j=i;j<M;j++)
        {
            sum=S[j]-S[i-1];
            if(sum==M)
                printf("%d %d\n",i,j);
        }
    }
    return 0;
}

股票买卖

题目链接:点这

题目描述:
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
输入格式:
第一行包含整数 N,表示数组长度。第二行包含 N 个不大于 109 的正整数,表示完整的数组。
输出格式:
输出一个整数,表示最大利润。

题目分析:
这道题解法多样,可以不用前缀和的方法解决。我是用贪心的做法,找到每个数前面最小的数,然后相减。比较减出来的结果,不断更新result,最后输出。

#include <iostream>
using namespace std;

int main()
{
    int n;
    cin >> n;
    int result = 0, Min = 0;
    cin >> Min; //第一个数
    while(-- n){
        int num;
        cin >> num;
        if(Min > num) Min = num;
        result = max(result, num - Min);
    }
    cout << result << endl;

    return 0;
}

激光炸弹

题目链接:点这

题目描述:
地图上有 N 个目标,用整数Xi,Yi表示目标在地图上的位置,每个目标都有一个价值Wi。注意:不同目标可能在同一位置。现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和x,y轴平行。求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式:
第一行输入正整数 N 和 R ,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。接下来N行,每行输入一组数据,每组数据包括三个整数Xi,Yi,Wi,分别代表目标的x坐标,y坐标和价值,数据用空格隔开。
输出格式:
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

题目分析:
这道题很明显是一道二维前缀和,求区域最值问题,和领地选择有一定的相似之处,先将mx和my赋初值为R,然后输入每一个目标的坐标的时候,分别选mx和++x,my和++y中更大的值来更新mx和my的值,来算出区域的最大边界。

    scanf("%d%d",&N,&R);
    int mx=R;
    int my=R;
    while(N--)
    {
        scanf("%d%d%d",&x,&y,&w);

        mx=max(mx,++x);
        my=max(my,++y);
        S[x][y]+=w;
    }

然后就是利用前缀和求出面积为R*R的正方形,算出此区域的价值总和,不断更新sum,最后输出最大的sum即为所求。

我的答案如下:

#include<iostream>
#include<stdio.h>

using namespace std;

const int Z=5010;
int S[Z][Z], sum=0;
int N, R, x, y, w;;

int main(){

    scanf("%d%d",&N,&R);
    int mx=R;
    int my=R;
    while(N--)
    {
        scanf("%d%d%d",&x,&y,&w);

        mx=max(mx,++x);
        my=max(my,++y);
        S[x][y]+=w;
    }

    for(int i=1;i<=mx;i++)
        for(int j=1;j<=my;j++)
            S[i][j] += S[i-1][j]+S[i][j-1]-S[i-1][j-1];

    for(int i=R;i<=mx;i++)
        for(int j=R;j<=my;j++)
            sum=max(sum,S[i][j]-S[i-R][j]-S[i][j-R]+S[i-R][j-R]);

    cout<<sum<<endl;

    return 0;
}

借教室

题目链接:点这

#include <bits/stdc++.h> //万能头文件,但是不建议使用
using namespace std;

const int N = 1000010;
#define ll long long
ll cr[N],sum[N],b[N],c[N];
int n,m;

struct book{
	int nums,start,end;
}book[N];

void insert(int l,int r,int c)
{
	b[l]+=c; b[r+1]-=c;
}

bool check(int d)
{
	for(int i=1;i<=n;i++) b[i] = c[i];
	fill(sum+1,sum+n+1,0);
	for(int i=1;i<=d;i++){
		insert(book[i].start,book[i].end,-book[i].nums);
	}
	for(int i=1;i<=n;i++){
		sum[i] = sum[i-1]+b[i];
		if(sum[i]<0) return false;
	}
	return true;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&cr[i]);
		insert(i,i,cr[i]);
	}
	for(int i=1;i<=n;i++) c[i] = b[i];
	for(int i=1;i<=m;i++) scanf("%d%d%d",&book[i].nums,&book[i].start,&book[i].end);
	int l = 1; int r =m;
	if(check(r)) { printf("0\n"); return 0;} //直接检查最后一个看是否满足要求
	while(l<r){//我们要找的答案是返回false的,需要的是不满足的最小值
		int mid = (l+r)/2;
		if(!check(mid)) r = mid;
		else l = mid+1;
	}
	printf("-1\n%d\n",l);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值