2024年山东省信息学小学组(CSP-X)复赛真题解析

​欢迎大家订阅我的专栏:算法题解:C++与Python实现
本专栏旨在帮助大家从基础到进阶 ,逐步提升编程能力,助力信息学竞赛备战!

专栏特色
1.经典算法练习:根据信息学竞赛大纲,精心挑选经典算法题目,提供清晰的代码实现与详细指导,帮助您夯实算法基础。
2.系统化学习路径:按照算法类别和难度分级,从基础到进阶,循序渐进,帮助您全面提升编程能力与算法思维。

适合人群:

  • 准备参加蓝桥杯、GESP、CSP-J、CSP-S等信息学竞赛的学生
  • 希望系统学习C++/Python编程的初学者
  • 想要提升算法与编程能力的编程爱好者

T1 购物

【题目来源】

洛谷:B4104 [CSP-X2024 山东] 购物 - 洛谷

【题目描述】

双十一,很多人在疯狂地购物。

商家推出了各种各样的优惠活动,吸引顾客购买更多的商品。

某商家推出如下的优惠活动:

该商家共有 n n n 件商品,单独购买第 i i i 件商品的费用为 a i a_i ai。顾客也可以花费 w w w 购买 一张优惠券,一张优惠券最多可兑换 m m m 件商品(无需额外付费)。顾客可以购买任意张优惠券;

如果最后商品不足 m m m 件,优惠券也可以使用。

求顾客购买完所有 n n n 件商品的最小费用。

【输入】

第一行有 3 3 3 个整数 n , m , w n,m,w n,m,w

第二行有 n n n 个整数,第 i i i 个为 a i a_i ai,表示第 i i i 件商品的费用。

【输出】

购买所有商品的最小费用。

【输入样例】

5 2 8
2 7 1 8 4

【输出样例】

15

【算法标签】

《洛谷 B4104 购物》 #山东# #CSP-X小学组# #2024#

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 使用长整型
const int N = 200005;  // 定义最大数组长度

int n, m, w, ans;  // n:物品数量,m:分组大小,w:每组最大价值,ans:总价值
int a[N];          // 存储物品价值

signed main() {
    cin >> n >> m >> w;  // 输入物品数、分组大小和每组价值上限
    
    // 读取物品价值并降序排序
    for (int i = 1; i <= n; i++) 
        cin >> a[i];
    sort(a + 1, a + 1 + n, greater<int>());
    
    int res = 0;  // 当前组累计价值
    for (int i = 1; i <= n; i++) {
        res += a[i];  // 将当前物品加入当前组
        
        // 每当凑满m个物品或处理完所有物品时
        if (i % m == 0 || i == n) {
            ans += min(res, w);  // 累加当前组价值(不超过w)
            res = 0;             // 重置当前组累计价值
        }
    }
    
    cout << ans << endl;  // 输出总价值
    return 0;
}

【运行结果】

5 2 8
2 7 1 8 4
15

T2 消灭怪兽

【题目来源】

洛谷:B4105 [CSP-X2024 山东] 消灭怪兽 - 洛谷

【题目描述】

怪兽入侵了地球!

为了抵抗入侵,人类设计出了按顺序排列好的 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 件武器的攻击力。

【输出】

一行一个整数表示答案。

【输入样例】

5 3
1 2 3 4 5

【输出样例】

7

【算法标签】

《洛谷 B4105 消灭怪兽》 #山东# #CSP-X小学组# #2024#

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 使用长整型
const int N = 1000005;  // 定义最大数组长度

int n, k, ans;      // n:数组长度,k:模数,ans:结果计数
int a[N], sa[N];    // a:原始数组,sa:前缀和数组
int cnt[N];         // 记录余数出现次数的数组

signed main() {
    cin >> n >> k;  // 输入数组长度和模数k
    
    // 计算前缀和数组
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sa[i] = sa[i-1] + a[i];  // sa[i]表示前i个元素的和
    }
    
    // 统计同余子数组数量
    for (int i = 0; i <= n; i++) {
        // 当前前缀和模k的余数
        int remainder = sa[i] % k;
        
        // 之前出现过相同余数的次数即为新增的子数组数量
        ans += cnt[remainder];
        
        // 更新该余数的出现次数
        cnt[remainder]++;
    }
    
    cout << ans << endl;  // 输出满足条件的子数组数量
    return 0;
}

【运行结果】

5 3
1 2 3 4 5
7

T3 翻硬币

【题目来源】

洛谷:B4106 [CSP-X2024 山东] 翻硬币 - 洛谷

【题目描述】

n n n 枚硬币从左到右依次排成一排,编号依次为 1 1 1 n n n。硬币的正面朝上用 0 0 0 表示,背面朝上用 1 1 1 表示,一开始所有的硬币都是正面朝上。

现进行 m m m 次操作,每次操作是把一个区间内的所有硬币翻过来:原来正面朝上的变为反面朝上,原来反面朝上的变为正面朝上。

i i i 次操作的区间 [ l i , r i ] [l_i,r_i] [li,ri]:表示把从第 l i l_i li 到第 r i r_i ri 枚之间的所有硬币都翻过来。

m m m 次操作后所有硬币从左到右依次组成的 01 01 01 数字序列。

【输入】

第一行整数 n n n 和整数 m m m,表示一共有 n n n 枚硬币和 m m m 次操作。

以下 m m m 行,每行两个正数数 l_i,ri,依次表示每次操作区间。

【输出】

m 次操作结束后 n 枚硬币从左到右依次组成的 01 数字序列。

【输入样例、

5 3
2 4
1 3
3 5

【输出样例】

10101

【算法标签】

《洛谷 B4106 翻硬币》 #山东# #CSP-X小学组# #2024#

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 200005;  // 定义最大数组长度
int n, m;              // n:二进制串长度,m:操作次数
int a[N];              // 差分数组

int main() {
    cin >> n >> m;  // 输入二进制串长度和操作次数
    
    // 处理每个操作区间
    for (int i = 1; i <= m; i++) {
        int l, r;
        cin >> l >> r;  // 输入操作区间
        a[l] ^= 1;      // 区间起始位置取反
        a[r + 1] ^= 1;  // 区间结束位置+1取反(差分思想)
    }
    
    // 计算前缀异或和,得到最终二进制串
    for (int i = 1; i <= n; i++) {
        a[i] ^= a[i - 1];  // 当前位与前一位异或
    }
    
    // 输出最终二进制串
    for (int i = 1; i <= n; i++) {
        cout << a[i];
    }
    cout << endl;
    
    return 0;
}

【运行结果】

5 3
2 4
1 3
3 5
10101

T4 刷题

【题目来源】

洛谷:B4107 [CSP-X2024 山东] 刷题 - 洛谷

【题目描述】

比赛之路多艰,做题方得提升。努力刷题的人在比赛中往往能取得很好的成绩,小红就是这样的人。

为了继续提升自己的编程实力,小红整理了一份刷题题单,并选中了题单中的 n n n 道编程题,将它们从 1 1 1 n n n 编号,计划用 m m m 天时间按照题目编号顺序做完所有的题目(一道题目只能在同一天完成,不可以使用多天完成同一道题目)。

在小红的计划中,她完成第 i i i 道题目的时间为 a i a_i ai。因为题目有难有易,小红做题时可以找好朋友小明帮忙解题,通过询问小明一道题目的解法,可以省去这个题目的做题时间。当然了,小红做题是为了提升自己,而不是提升小明。因此小红决定一天最多求助小明一次。

本题 m m m 天中,小红做题时间最长一天的总耗时定义为 T T T(小明帮忙做的题目不计入小红的做题总时间)。请你帮小红求出 T T T 的最小值是多少?

【输入】

第一行两个正整数 n , m n,m n,m 分别表示小红做的题目以及小红刷完这些题目计划所用天数。

第二行 n n n 个正整数,分别表示每个题目解题所用时间 a i a_i ai

【输出】

输出仅一行, m m m 天中耗时最长一天的总耗时 T T T 的最小值。

【输入样例】

4 2
1 2 3 3

【输出样例】

3

【算法标签】

《洛谷 B4107 刷题》 #山东# #CSP-X小学组# #2024#

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;  // 定义最大任务数量
int n, m;              // n:任务数量, m:最大分组数
int a[N];              // 存储每个任务的耗时

// 检查函数:判断是否可以在每组耗时不超过x的情况下,将任务分成不超过m组
bool check(int x) {
    int cnt = 0;       // 当前分组数
    int tmp = 0;       // 当前组的累计耗时
    int maxn = 0;      // 当前组中的最大任务耗时
    
    for (int i = 1; i <= n; i++) {
        tmp += a[i];                // 将当前任务加入当前组
        maxn = max(maxn, a[i]);     // 更新当前组最大任务耗时
        
        // 如果当前组累计耗时超过x
        if (tmp > x) {
            cnt++;                  // 增加分组数
            tmp -= maxn;            // 移除当前组最大任务(贪心策略)
            
            // 尝试将后续任务加入当前组(不超过x)
            int j = i + 1;
            while (j <= n && tmp + a[j] <= x) {
                tmp += a[j++];      // 加入后续任务
            }
            
            maxn = 0;              // 重置当前组最大任务耗时
            tmp = 0;               // 重置当前组累计耗时
            i = j - 1;             // 更新索引(因为j已经指向下一个未处理任务)
        }
    }
    
    // 处理最后一组
    if (tmp > 0) cnt++;
    
    // 判断分组数是否满足要求
    return cnt <= m;
}

int main() {
    cin >> n >> m;  // 输入任务数量和最大分组数
    
    // 输入每个任务的耗时
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    
    // 二分查找最小可能的最大组耗时
    int l = 0, r = 1e9;  // 初始化左右边界
    while (l < r) {
        int mid = (l + r) >> 1;  // 取中间值
        
        // 检查mid是否满足条件
        if (check(mid)) {
            r = mid;    // 满足则尝试更小的值
        } else {
            l = mid + 1; // 不满足则需要更大的值
        }
    }
    
    // 输出结果(最小可能的最大组耗时)
    cout << l << endl;
    
    return 0;
}

【运行结果】

4 2
1 2 3 3
3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值