欢迎大家订阅我的专栏:算法题解: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