CSP-X2024山东小学组T2:消灭怪兽

题目链接

CSP-X2024山东小学组T2:消灭怪兽

题目描述

怪兽入侵了地球!

为了抵抗入侵,人类设计出了按顺序排列好的 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,k100
  • 40% 的数据, n , k ≤ 10000 , 1 ≤ a i ≤ k n, k ≤ 10000,1 ≤ a_i ≤ k n,k10000,1aik
  • 另外存在10% 的数据, k = 2 k = 2 k=2
  • 另外存在10% 的数据,所有的 a i a_i ai 均相等。
  • 100% 的数据, 1 ≤ n ≤ 1 0 6 1 ≤ n ≤ 10^6 1n106 , 2 ≤ k ≤ 1 0 6 2 ≤ k ≤ 10^6 2k106 , 1 ≤ a i ≤ 1 0 9 1 ≤ a_i ≤ 10^9 1ai109

算法思想

朴素版前缀和

根据题目描述,只需要预处理出前缀和,然后枚举区间的开始位置 L L L和结束位置 R R R,判断 S [ R ] − S [ L − 1 ] S[R]-S[L-1] S[R]S[L1]是否为 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 1n106,可以拿到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[L1]一定是 11 11 11的倍数,也就是说 ( S [ R ] − S [ L − 1 ] )   m o d   11 = 0 (S[R] - S[L - 1])\ mod\ 11=0 (S[R]S[L1]) 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×(cnt1)/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 1n106 , 2 ≤ k ≤ 1 0 6 2 ≤ k ≤ 10^6 2k106

代码实现

#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;//注意,余数为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) //至少有两个余数为i的位置
        	ans += (LL) cnt[i] * (cnt[i] - 1) / 2; 
    }
    cout << ans << endl;
}
可以从以下引用中获取CSP - X2024相关的C++刷题题目及题解: - 2024CSP - X T4题:小红为提升编程实力,整理刷题题单,选中n道编程题并从1到n编号,计划用m天按题目编号顺序做完所有题目,且一道题目只能在同一天完成 [^1]。 - CSP - X2024山东小学组T4:内容与上述类似,小红为提升编程实力,整理题单选n道题编号,计划用m天按序做完,一道题一天完成 [^2]。 同时还提供了两份题解代码: ```cpp // 2024山东小学组CSP - X T4刷题题解代码 #include<bits/stdc++.h> using namespace std; const int N = 1e5 + 10; long long n, m; int a[N]; bool check(int x) { int s = 0, t = 1, maxn = 0; for(int i = 1; i <= n; i++) { maxn = max(maxn, a[i]); if(s + a[i] - maxn <= x) { s += a[i]; } else { t++; s = a[i]; maxn = a[i]; } } return t <= m; } int ef(int l, int r) { int mid, ans = -1; while(l <= r) { mid = (l + r) / 2; if(check(mid)) { ans = mid; r = mid - 1; } else { l = mid + 1; } } return ans; } int main() { cin >> n >> m; if(m > n) { cout << 0; return 0; } for(int i = 1; i <= n; i++) cin >> a[i]; cout << ef(1, 1e9); return 0; } ``` ```cpp // CSP - X2024解题报告(T4)代码 #include<bits/stdc++.h> using namespace std; #define in long long in b[200005], ma = 0; in n, m, l, a[200005], r; bool check(in mid) { in maxx = 0, k = 0, cnt = 0, ans = 0; for(int i = 1; i <= n; i++) { maxx = max(maxx, a[i]); if(cnt + a[i] - maxx > mid && cnt + a[i] - max(maxx, a[i]) > mid) { ans++; cnt = a[i]; k = i; maxx = a[i]; continue; } else { cnt = 0; maxx = 0; if(i == n) k = n; ans++; } } ans++; return ans <= m; } int main() { freopen("question.in", "r", stdin); freopen("question.out", "w", stdout); cin >> n >> m; for(in i = 1; i <= n; i++) { cin >> a[i]; ma += a[i]; } in l = 0, r = ma; while(l < r) { int mid = (l + r) / 2; if(check(mid)) { r = mid; } else l = mid + 1; } cout << l; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

少儿编程乔老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值