【蓝桥杯】K倍区间及其引申题

目录

【蓝桥杯】K倍区间

【蓝桥杯引申题】激光炸弹


【蓝桥杯】K倍区间


给定一个长度为 NN 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式

第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式

输出一个整数,代表 K 倍区间的数目。

数据范围

1≤N,K≤100000
1≤Ai≤100000

输入样例:

5 2
1
2
3
4
5

输出样例:

6

思路:

        不难想到两层for循环的暴力做法。但N为1e5 两层for循环肯定会TLE。

        本文介绍一个更加快速的做法:

        首先看到连续子序列的和,我们很自然的就可以想到前缀和,前缀和可以帮助我们优化掉两层for循环变为一层for循环!子序列s[i] ~ s[j] 的和用前缀和表示可以表示为s[j] - s[i - 1],那么如果这个子序列为K倍区间的话, 可以表示为

                (s[j] - s[i - 1]) % k == 0 ,转化一下 s[j] % k == s[i - 1] %k

        即s[i - 1] 与 s[j] 同余,想明白这些之后,我们只要每次遇到一个同余序列的时候,res 加上cnt,然后让cnt自增一次(解释:为什么res += cnt 后 cnt 再自增,举个例子,前面有两个数余数为x, 如果遇到了第三个数余数为x, 那么第三个余数可以跟第一个和第二个数组成两个序列。自增是为了下一个余数为x的数的计算)问题得以解决。

思路的实现:

1.求前缀和

for(int i = 1; i <= n; i++)
{
    scanf("%d", &s[i]);
    s[i] += s[i - 1];
}

2.记录k倍区间

//细节:长度为0的子序列 mod k 结果为 0, 所以记cnt[0]为1
//cnt[x] 表示余数为x的s[i]出现的次数
cnt[0] = 1;
for(int i = 1; i <= n; i++)
{
    res += cnt[s[i] % k];
    cnt[s[i] % k]++;
}
    

完整代码(C++):

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

using namespace std;

const int N = 1e5 + 10;
//结果可能爆int 用long long存储
long long s[N], cnt[N]; 
long long res;

int main()
{
    int n, k;
    cin >> n >> k;
    
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &s[i]);
        s[i] += s[i - 1];
    }
    
    cnt[0] = 1;
    for(int i = 1; i <= n; i++)
    {
        res += cnt[s[i] % k];
        cnt[s[i] % k]++;
    }
    
    cout << res << endl;
    
    return 0;
}

【蓝桥杯引申题】激光炸弹


地图上有 N 个目标,用整数 Xi,Yi 表示目标在地图上的位置,每个目标都有一个价值 Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式

第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式

输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤1e9
0<N≤10000
0≤Xi,Yi≤5000
0≤Wi≤1000

输入样例:

2 1
0 0 1
1 1 1

输出样例:

1

思路:

        简单的二维前缀和,但是有一些细节需要处理。

        首先看输入样例,可以看出下标是从 0 开始的,但是二维前缀和的读取一般都是从 1 开始,所以当读取完x, y之后, x, y自增一次然后再进行价值的读取

        再看题目中的注意事项:不同目标可能在同一位置。所以在读取的时候数据应该是 x += y的形式,而不是单纯的读取

        再看数据范围 R 最大是 1e9, 然而 X,Y 最大只有5000, 所以 R 取大于5000是没有意义的,而且不处理R的话还可能导致数组越界的问题, 在读取完R之后要将R处理在[1,5001]这个范围内(解释:为什么是5001,而不是5000--->因为x的范围是[0,5000],前面说过要自增一次所以x的范围变为[1,5001])

        内存超限问题:本题的内存最大限制是 168 MB 理想情况下可以开 168 * 2^20 / 4 = 44040192(四千多万) 个 int。 如果开两个边长为5000的int型二维数组,需要五千万int,一定会内存超限,所以本题的前缀和处理只能用一个二维数组处理!

思路实现:

1.数据的读取与处理

    int n, r;
    cin >> n >> r;
    r = min(r, 5001);
    for(int i = 0; i < n; i++)
    {
        scanf("%d%d", &x, &y);
        x++, y++;
        scanf("%d", &w);
        s[x][y] += w;
    }

2.求前缀和

    for(int i = 1; i < 5010; i++)
        for(int j = 1; j < 5010; j++)
        {
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }

3.求最大值

    int res = 0;
    for(int i = r; i < 5010; i++)
        for(int j = r; j < 5010; j++)
        {
            int temp = s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r];
            res = max(temp, res);
        }

完整代码(C++):

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

using namespace std;

const int N = 5000 + 10;

int x, y, w, s[N][N];

int main()
{
    int n, r;
    cin >> n >> r;
    r = min(r, 5001);
    for(int i = 0; i < n; i++)
    {
        scanf("%d%d", &x, &y);
        x++, y++;
        scanf("%d", &w);
        s[x][y] += w;
    }

    for(int i = 1; i < 5010; i++)
        for(int j = 1; j < 5010; j++)
        {
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
        }
    
    int res = 0;
    for(int i = r; i < 5010; i++)
        for(int j = r; j < 5010; j++)
        {
            int temp = s[i][j] - s[i - r][j] - s[i][j - r] + s[i - r][j - r];
            res = max(temp, res);
        }
    
    cout << res << endl;
    
    return 0;
}

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

dkl2024

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

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

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

打赏作者

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

抵扣说明:

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

余额充值