目录
【蓝桥杯】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;
}