题目链接
题目名称
题目描述
怪兽入侵了地球!
为了抵抗入侵,人类设计出了按顺序排列好的 n n n 件武器,其中第 i i i 件武器的攻击力为 a i a_i ai,可以造成 a i a_i ai 的伤害。
武器已经排列好了,因此不能改变顺序。某件武器可以单独攻击,也可以与相邻的武器进行组合攻击。
具体来说,每次你可以把相邻的若干个(可以为
1
1
1 个,即不进行组合)连续
的武器组合起来进行攻击,则攻击力为这些连续的武器攻击力之和。
来自外星的怪兽拥有无敌护盾,不会受到任何伤害。但是人类在交战过程中发现怪兽有个致命的弱点:每次当受到 k k k 或 k k k 的倍数的伤害时,怪兽的无敌护盾就能被打破。
请你帮助人类求出有多少种组合武器的方案,使得造成的伤害能打破怪兽的无敌护盾。
输入格式
第一行两个正整数 n , k n, k n,k 如题所述; 第二行为 n n n 个正整数,其中第 i i i 个数 a i a_i ai 表示第 i i i 件武器的攻击力。
输出格式
一行一个整数表示答案。
样例
样例输入 #1
5 3
1 2 3 4 5
样例输出 #1
7
样例输入 #2
10 11
1 4 8 10 16 19 21 25 30 43
样例输出 #2
7
样例输入 #3
6 2
2 2 2 2 2 2
样例输出 #3
21
提示
【样例1解释】
k
=
3
k=3
k=3,而区间 [1, 2],[1, 3],[1, 5],[2, 4],[3, 3],[3, 5],[4, 5] 的区间
和均为
3
3
3 或
3
3
3 的倍数,故一共有
7
7
7 种方案。
【数据范围】
- 20% 的数据, n , k ≤ 100 n, k ≤ 100 n,k≤100;
- 40% 的数据, n , k ≤ 10000 , 1 ≤ a i ≤ k n, k ≤ 10000,1 ≤ a_i ≤ k n,k≤10000,1≤ai≤k;
- 另外存在10% 的数据, k = 2 k = 2 k=2;
- 另外存在10% 的数据,所有的 a i a_i ai 均相等。
- 100% 的数据, 1 ≤ n ≤ 1 0 6 1 ≤ n ≤ 10^6 1≤n≤106 , 2 ≤ k ≤ 1 0 6 2 ≤ k ≤ 10^6 2≤k≤106 , 1 ≤ a i ≤ 1 0 9 1 ≤ a_i ≤ 10^9 1≤ai≤109。
算法思想
朴素版前缀和
根据题目描述,只需要预处理出前缀和,然后枚举区间的开始位置 L L L和结束位置 R R R,判断 S [ R ] − S [ L − 1 ] S[R]-S[L-1] S[R]−S[L−1]是否为 k k k的倍数就可以了。
时间复杂度
- 处理前缀和的时间复杂度为 O ( n ) O(n) O(n)
- 枚举开始和结束位置的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
总的时间复杂度为 O ( n 2 ) O(n^2) O(n2),其中 1 ≤ n ≤ 1 0 6 1 ≤ n ≤ 10^6 1≤n≤106,可以拿到60分。
代码实现
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 5;
LL a[N], s[N];
int main()
{
int n, k;
cin >> n >> k;
LL ans = 0;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= i; j ++)
{
LL x = s[i] - s[j - 1];
if(x % k == 0) ans ++;
}
}
cout << ans << endl;
}
算法优化
连续区间
[
L
,
R
]
[L, R]
[L,R]中所有数的和是
11
11
11的倍数,那么前缀和数组中
S
[
R
]
−
S
[
L
−
1
]
S[R] - S[L - 1]
S[R]−S[L−1]一定是
11
11
11的倍数,也就是说
(
S
[
R
]
−
S
[
L
−
1
]
)
m
o
d
11
=
0
(S[R] - S[L - 1])\ mod\ 11=0
(S[R]−S[L−1]) mod 11=0。根据这个性质,不妨将前缀和数组中的每个值
m
o
d
11
mod\ 11
mod 11,得到一个余数数组,如下图所示。
如果存在两个位置
x
x
x和
y
y
y余数相同,它们相减的差为
0
0
0,那么说明序列中区间
[
x
+
1
,
y
]
[x+1,y]
[x+1,y]中所有数的和为
11
11
11的倍数。
这样,只需要统计一下,余数数组中值为 i i i的个数,不妨设有 c n t cnt cnt个,那么任意两个都可以构成一个区间,其中所有数为 11 11 11的倍数,那么对答案的贡献为: c n t × ( c n t − 1 ) / 2 cnt\times(cnt-1)/2 cnt×(cnt−1)/2。
时间复杂度
- 处理前缀和的时间复杂度为 O ( n ) O(n) O(n)
- 遍历所有余数的时间复杂度为 O ( k ) O(k) O(k)
总的时间复杂度为 O ( n + k ) O(n+k) O(n+k),其中 1 ≤ n ≤ 1 0 6 1 ≤ n ≤ 10^6 1≤n≤106 , 2 ≤ k ≤ 1 0 6 2 ≤ k ≤ 10^6 2≤k≤106 。
代码实现
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 5;
int a[N], s[N], cnt[N];
int main()
{
int n, k;
cin >> n >> k;
cnt[0] = 1;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
s[i] = (s[i - 1] + a[i]) % k;
cnt[s[i]] ++;
}
LL ans = 0;
for(int i = 0; i < k; i ++)
{
if(cnt[i] > 1) ans += (LL) cnt[i] * (cnt[i] - 1) / 2;
}
cout << ans << endl;
}