[蓝桥杯 2024 省 A] 训练士兵 的题解
记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求计算使所有士兵达到所需训练次数的最小总花费:每次组团训练为全体士兵各增加一次训练并花费 金币,也可以单独为某名士兵训练(每次
金币)。核心解法是枚举组团训练次数
,用排序与后缀和快速计算每种
下的花费并取最小值。
分析
核心观察
若购买 次组团训练,士兵
还差
次训练需单独完成,总花费为
;只需枚举
,因为超过最大需求后每多一次组团只会增加
而无任何节省。
思路
对固定的 :
按 升序排序士兵,预处理后缀和
与
。枚举
时,用指针找到第一个满足
的下标
(该下标之前士兵已不再需要单独训练),则:
取所有 中的最小值即为答案。直观理解:多买一次组团的花费是
,节省的是当前仍需单独训练的士兵的
之和,最优
位于两者平衡处。
具体示例
以样例为例:,
,士兵
为
、
、
。按
排序后为
,后缀和
、
。枚举:
时花费
;
时
;
时
;
时
;
时
。最小花费为
,与样例一致。
算法步骤
- 读入
n、S以及所有(p_i, c_i),记录最大需求maxC。 - 将士兵按
c升序排序。 - 预处理后缀和数组
suf_cp、suf_p。 - 枚举
k从到
maxC,用指针ptr维护第一个满足的下标,计算花费
cost为,并更新最小值。
- 输出最小花费。
复杂度分析
时间:
空间:
实现注意事项
- 花费可能很大:
,
,需使用 64 位整数(long long)。
只需枚举到最大需求
,超过后每多一次组团只会增加
而无节省。
- 指针
ptr随单调右移,总移动次数为
,故枚举部分整体为
。
- 本题
,枚举全部
可行;若
更大,可利用花费函数的分段线性性质,只检查断点
与
附近的候选值。
源代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
long long S;
cin >> n >> S;
vector<pair<long long, long long>> a(n);
long long maxC = 0;
for (int i = 0; i < n; i++) {
long long p, c;
cin >> p >> c;
a[i] = {c, p};
maxC = max(maxC, c);
}
sort(a.begin(), a.end());
vector<long long> suf_cp(n + 1, 0), suf_p(n + 1, 0);
for (int i = n - 1; i >= 0; i--) {
suf_cp[i] = suf_cp[i + 1] + a[i].first * a[i].second;
suf_p[i] = suf_p[i + 1] + a[i].second;
}
long long ans = LLONG_MAX;
int ptr = 0;
for (long long k = 0; k <= maxC; k++) {
while (ptr < n && a[ptr].first <= k) ptr++;
long long cost = k * S + suf_cp[ptr] - k * suf_p[ptr];
ans = min(ans, cost);
}
cout << ans << '\n';
return 0;
}
n, S = map(int, input().split())
soldiers = []
for _ in range(n):
p, c = map(int, input().split())
soldiers.append((c, p))
soldiers.sort()
m = n
suf_cp = [0] * (m + 1)
suf_p = [0] * (m + 1)
for i in range(m - 1, -1, -1):
c, p = soldiers[i]
suf_cp[i] = suf_cp[i + 1] + c * p
suf_p[i] = suf_p[i + 1] + p
maxC = soldiers[-1][0]
ans = float("inf")
ptr = 0
for k in range(maxC + 1):
while ptr < m and soldiers[ptr][0] <= k:
ptr += 1
cost = k * S + suf_cp[ptr] - k * suf_p[ptr]
ans = min(ans, cost)
print(ans)
评论