ゆずりさ
2901 字
15 分钟
蓝桥杯3
题目链接
A. 填空问题
题解
暴力枚举
枚举 + 去重
for (int i = 0; i < 20; i++) { for (int j = 0; j < 21; j++) { vec.push_back({i, j}); } } for (int i = 0; i < vec.size(); i++) { for (int j = i + 1; j < vec.size(); j++) { int x1 = vec[i].first, y1 = vec[i].second; int x2 = vec[j].first, y2 = vec[j].second; int A = x2 - x1, B = y1 - y2, C = x1 * y2 - x2 * y1; int gcdd = gcd(gcd(A, B), C); s.insert({{B / gcdd, A / gcdd}, C / gcdd}); } } cout << s.size() << endl;
找出 的所有因子,枚举合法对
ll n = 2021041820210418; vector<ll> a; for (ll i = 1; i * i <= n; i++) { if (n % i == 0) { a.push_back(i); if (i * i != n) a.push_back(n / i); } } int ans = 0; for (auto x: a) { for (auto y: a) { if (n % (x * y) == 0) { ans++; } } } cout << ans << endl;
最短路模板题
// author: Qwen2.5-Max #include <bits/stdc++.h> using namespace std; // 计算最大公约数 (GCD) int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } // 计算最小公倍数 (LCM) int lcm(int a, int b) { return (a / gcd(a, b)) * b; } // Dijkstra 算法求最短路径 long long dijkstra(int n) { // 定义优先队列 (最小堆),存储 (距离, 结点) priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<>> pq; // 定义距离数组,初始化为无穷大 vector<long long> dist(n + 1, LLONG_MAX); // 起点为结点 1,距离为 0 dist[1] = 0; pq.push({0, 1}); while (!pq.empty()) { auto [current_dist, u] = pq.top(); pq.pop(); // 如果当前距离大于记录的距离,跳过 if (current_dist > dist[u]) continue; // 更新邻居的距离 for (int v = max(1, u - 21); v <= min(n, u + 21); ++v) { if (v == u) continue; // 不考虑自己 // 计算边的权重 (LCM) int weight = lcm(u, v); // 更新最短距离 if (dist[v] > dist[u] + weight) { dist[v] = dist[u] + weight; pq.push({dist[v], v}); } } } // 返回从结点 1 到结点 n 的最短距离 return dist[n]; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n = 2021; // 图的结点数 cout << dijkstra(n) << "\n"; return 0; }
B. 时间显示
题解
将毫秒转化成时分秒并补全前导
代码
// author: Qwen2.5-Max
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long timestamp;
cin >> timestamp;
// 转换为秒,并取一天内的秒数
long long seconds = timestamp / 1000; // 舍去毫秒
long long seconds_in_day = seconds % (24 * 60 * 60);
// 提取小时、分钟和秒
int hours = seconds_in_day / 3600;
int remaining_seconds = seconds_in_day % 3600;
int minutes = remaining_seconds / 60;
int secs = remaining_seconds % 60;
// 格式化输出 HH:MM:SS
printf("%02d:%02d:%02d\n", hours, minutes, secs);
return 0;
}
C. 砝码称重
题意
将 个砝码凑成尽可能多的重量
题解
- 状态表示:
- 使用一个布尔数组
dp
表示当前可以称出的重量集合。 dp[w] = true
表示重量 可以被称出。
- 使用一个布尔数组
- 动态规划更新:
- 对于每个砝码 ,我们有两种选择:
- 将砝码放在天平的一侧(增加重量)。
- 将砝码放在天平的另一侧(减少重量)。
- 更新规则为:如果当前可以称出重量 ,则可以扩展到 和 。
- 对于每个砝码 ,我们有两种选择:
- 优化空间复杂度:
- 因为砝码总重不超过 ,我们可以使用一个布尔数组
dp
,大小为 ,表示所有可能的重量。
- 因为砝码总重不超过 ,我们可以使用一个布尔数组
- 最终结果:
- 统计
dp
数组中值为true
的元素个数,即为可以称出的不同重量的种类数。
- 统计
代码
// author: Qwen2.5-Max
#include <bits/stdc++.h>
using namespace std;
int main() {
int N;
cin >> N;
vector<int> weights(N);
for (auto &w : weights) cin >> w;
// 计算砝码总重的最大值
int max_weight = 0;
for (int w : weights) max_weight += w;
// 初始化 dp 数组
vector<bool> dp(max_weight + 1, false);
dp[0] = true; // 初始时可以称出重量 0
// 动态规划更新
for (int i = 0; i < N; ++i) {
int W = weights[i];
// 创建临时数组,避免覆盖当前状态
vector<bool> temp(dp);
for (int w = 0; w <= max_weight; ++w) {
if (dp[w]) {
// 增加重量
if (w + W <= max_weight) temp[w + W] = true;
// 减少重量
if (w >= W) temp[w - W] = true;
else temp[W - w] = true;
}
}
// 更新 dp 数组
dp = move(temp);
}
// 统计可以称出的不同重量的种类数
int count = 0;
for (int w = 1; w <= max_weight; ++w) {
if (dp[w]) ++count;
}
cout << count << "\n";
return 0;
}
D. 123
题意
计算形如 无限长数列的区间和
题解
关键点
数列分组:
- 第 组包含 个数字,分别是 。
- 数列中第 个数字所属的组可以通过数学公式快速确定。
前缀和优化:
- 由于 和 的范围可能非常大(高达 ),直接模拟数列不可行。
- 我们需要通过数学公式计算任意位置的前缀和,从而高效地求解区间和。
公式推导:
- 第 组的总和为 。
- 前 组的总和为 。
- 通过二分查找,可以快速定位某个位置属于哪一组。
解题思路
定位组号:
- 使用二分查找确定某个位置 所属的组号 。
- 每组的起始位置为 。
计算前缀和:
- 前 组的总和为:
- 对于任意位置 ,先找到其所属的组 ,然后计算其在组内的偏移量,得到其值。
区间和计算:
- 区间和可以通过前缀和公式快速计算:
代码
// author: Qwen2.5-Max
#include <bits/stdc++.h>
using namespace std;
// 计算前 m 组的总和
long long prefix_sum_group(long long m) {
return m * (m + 1) * (m + 2) / 6;
}
// 找到位置 n 所属的组号 k
long long find_group(long long n) {
long long left = 1, right = 2e6; // k 的最大值约为 sqrt(2e12)
while (left < right) {
long long mid = (left + right) / 2;
if (mid * (mid + 1) / 2 < n) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
// 计算位置 n 的前缀和
long long prefix_sum_position(long long n) {
if (n == 0) return 0;
long long k = find_group(n); // 找到 n 所属的组号
long long start_k = k * (k - 1) / 2 + 1; // 第 k 组的起始位置
long long offset = n - start_k; // 在第 k 组中的偏移量
long long sum_prev_groups = prefix_sum_group(k - 1); // 前 k-1 组的总和
long long sum_current_group = (offset + 1) * (offset + 2) / 2; // 当前组的部分和
return sum_prev_groups + sum_current_group;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
long long l, r;
cin >> l >> r;
// 计算区间和
long long result = prefix_sum_position(r) - prefix_sum_position(l - 1);
cout << result << "\n";
}
return 0;
}
E. 负载均衡
题解
- 模拟任务调度:
- 使用一个数组
remaining
记录每台计算机的当前剩余运算能力。 - 使用一个优先队列(最小堆)为每台计算机维护正在运行的任务,按照任务的结束时间排序。
- 使用一个数组
- 任务分配逻辑:
- 对于每个任务:
- 检查当前计算机是否有已经结束的任务,并释放它们占用的算力。
- 判断当前计算机的剩余运算能力是否足够支持新任务。
- 如果足够,将任务加入优先队列,并更新剩余运算能力;否则输出 。
- 对于每个任务:
- 优化点:
- 使用优先队列(最小堆)确保每次可以快速找到最早结束的任务。
- 时间复杂度主要由优先队列的操作决定,单次操作的时间复杂度为 。
代码
// author: Qwen2.5-Max
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
// 每台计算机的初始运算能力
vector<long long> remaining(n + 1);
for (int i = 1; i <= n; ++i) cin >> remaining[i];
// 每台计算机的任务队列 (优先队列,存储 {结束时间, 算力消耗})
vector<priority_queue<pair<int, int>, vector<pair<int, int>>, std::greater<>>> tasks(n + 1);
// 处理每个任务
for (int i = 0; i < m; ++i) {
int a, b, c, d;
cin >> a >> b >> c >> d;
// 当前计算机编号为 b
auto &pq = tasks[b];
// 释放已经结束的任务
while (!pq.empty() && pq.top().first <= a) {
remaining[b] += pq.top().second; // 恢复算力
pq.pop();
}
// 判断是否可以接受新任务
if (remaining[b] >= d) {
// 接受任务,更新剩余算力
remaining[b] -= d;
pq.push({a + c, d}); // 加入任务队列 (结束时间为 a + c)
cout << remaining[b] << "\n";
} else {
// 无法接受任务
cout << "-1\n";
}
}
return 0;
}
F. 异或数列
题意
给定长度为 的数列,双方每回合各取一个值异或,求谁最后的值最大
题解
- 数列的所有元素 的异或和为
- 如果 ,则无论 Alice 和 Bob 如何分配,最终两人的数值必然相等,结果为平局
- 否则,由于高位 的价值显然大于低位 的价值,故从高位向低位枚举:
- 假设当前位是 的数量是 ,则 先手选择该数字后,由于低位不可能超过高位,因此 必胜
- 假设当前位为 的数量是偶数,一个偶数只能拆分成两个奇偶性相同的非负整数,所以 和 在该位的值一定相同,故不影响胜负
- 假设当前位为 的数量是奇数,一个奇数只能拆分成两个奇偶性不同的非负整数,那么当前这一位一定能分出胜负,因此谁拿到奇数个谁就获胜。如果所有数这一位都是 ,那么先手获胜,否则后手可以选择这一位为 的数改变先后手顺序,因此当 为偶数时, 获胜,否则 获胜。
void idol_produce(int testCase) {
/*Code Here*/
int n;
cin >> n;
vector<int> a(32);
int sum = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum ^= x;
for (int j = 0; j < 32; j++) {
if (x & (1 << j))
a[j]++;
}
}
if (!sum) {
cout << "0\n";
return;
}
for (int i = 31; i >= 0; i--) {
if (a[i] % 2 == 0) {
continue;
}
if (a[i] == 1) {
cout << "1\n";
return;
}
if((n - a[i]) % 2 == 0){
cout << "1\n";
}
else{
cout << "-1\n";
}
return;
}
}
G. 巧克力
题意
我们需要帮助小蓝购买巧克力,使得他能够吃 天,并且总花费最小。每种巧克力有以下属性:
- 单价:每块巧克力的价格为 。
- 保质期:巧克力只能在接下来的 天内食用。
- 数量:每种巧克力最多可以购买 块。
题解
为了尽可能地减小吃巧克力的花费,用一个堆来维护当前没有过期的巧克力中的最小值,从最后一天开始向前枚举,并将当天没有过期的巧克力加入堆中,每天取堆的最小值,并判断数量是否耗尽即可。
如果从第一天枚举,堆中的合法巧克力种类是不断减少的,那么会出现因为优先选择保质期长的最小值而无法选择保质期短的次小值,而从后往前枚举堆中的合法巧克力种类是增多而非减少的,故不会出现这种问题
代码
struct node {
int a, b;
mutable int c;
bool operator<(const node &rhs) const {
return a > rhs.a;
}
};
void idol_produce(int testCase) {
/*Code Here*/
int x, n;
cin >> x >> n;
priority_queue<node> q;
vector<node> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i].a >> a[i].b >> a[i].c;
}
sort(a.begin() + 1, a.end(), [](node &lhs, node &rhs) {
return lhs.b < rhs.b;
});
ll ans = 0;
for (int i = x; i >= 1; i--) {
while (!a.empty() && a.back().b >= i) {
q.push(a.back());
a.pop_back();
}
while (q.top().c == 0) {
q.pop();
}
if (q.empty()) {
cout << -1 << '\n';
return;
}
ans += q.top().a;
q.top().c--;
}
cout << ans << '\n';
}