Educational Codeforces Round 90 题解
有几年没有打 CF 了,最近特别想做一下算法题怀念一下。
A. Donut Shops
背景:商店 X 卖 1 份油炸圈饼 a 元,商店 Y 卖 b 份油炸圈饼 c元(只能买 b 的倍数份这样批发)
问题:在 X 商店买多少份价格严格小于 Y 商店?在 Y 商店买多少份价格严格小于 X 商店?有多种可行解输出任何一个,否则输出 -1.
思路:在 X 商店买肯定是单买 1 份最划算,在 Y 商店肯定直接批发 b 份最划算,然后计算下两边价格就是了
#include <bits/stdc++.h> using namespace std; using ll = long long; void run() { ll a, b, c; cin >> a >> b >> c; if (a < c) { cout << 1 << " "; } else { cout << -1 << " "; } if (c < a * b) { cout << b << "\n"; } else { cout << -1 << "\n"; } } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::setvbuf(stdout, nullptr, _IOFBF, BUFSIZ); int T; cin >> T; while (T-- > 0) { run(); } }
B. 01 Game
背景:有一个 0 或 1 组成的字符串,每次可以拿走其中的 01 或者 10,A 和 B 轮流拿,A先拿,直到拿不动了就输了。
问题:A 和 B 都按照最优方案去哪,A 是否能赢?
思路:直接贪心去拿串中的第一个 01 或者 10;可以分情况讨论一下,这种策略不会比其他策略差;然后记忆化搜索一下输出结果就好了
#include <bits/stdc++.h> using namespace std; using ll = long long; unordered_map<string, int> cache; /** * 返回 2 为必败态,返回 1 为必赢态 */ int calc(string &s) { int &ans = cache[s]; if (ans > 0) return ans; if (s.empty() || s.find('0') == string::npos || s.find('1') == string::npos) { return ans = 2; } ans = 2; for (int i = 1; i < s.length(); ++i) { if (s[i] != s[i - 1]) { string cur = s.substr(0, i - 1) + s.substr(i + 1, s.length()); if (calc(cur) == 2) ans = 1; break; } } return ans; } void run() { string s; cin >> s; cout << (calc(s) == 1 ? "DA" : "NET") << "\n"; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::setvbuf(stdout, nullptr, _IOFBF, BUFSIZ); int T; cin >> T; while (T-- > 0) { run(); } }
C. Pluses and Minuses
背景:给你一个函数,参数为一个只有 + 或者 - 组成的字符串,问你函数执行结果 res 是啥?
分析:这个函数转换下意思,即 初始值为0,然后按字符串不断执行 +1 或 -1,直到变成负数执行了多少次。然后初始值变为 1 2 ...,直到可以全部把字符串执行下来。问你总共执行了多少次。
思路:则可以计算出到每一个指令 (+1 or -1) 时所需的最小的初始值,可以看到这个所需初始值是递增的。然后遍历初始值,二分找到最远能执行到哪个指令,累加即可。
#include <bits/stdc++.h> using namespace std; using ll = long long; void run() { string s; cin >> s; int cur = 0, need = 0; vector<int> v(s.length()); for (int i = 0; i < s.length(); ++i) { if (s[i] == '-') { cur -= 1; } else { cur += 1; } if (cur < 0) { need = max(need, -cur); } v[i] = need; } ll ans = 0; for (int i = 0; i <= v.back(); ++i) { int pos = static_cast<int>(upper_bound(v.begin(), v.end(), i) - v.begin()) + 1; ans += min(pos, (int) s.length()); } cout << ans << "\n"; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::setvbuf(stdout, nullptr, _IOFBF, BUFSIZ); int T; cin >> T; while (T-- > 0) { run(); } }
D. Maximum Sum on Even Positions
背景:给你一个数组,你可以 reverse 其中的任意一段一次,问你这样操作后的“数组中偶数下标对应的数之和的最大值”是多少
思路:
翻转一段肯定是翻转偶数长度,否则没有意义了。
比如你翻转 1 7 3 4 变成 4 3 7 1 ,获得的收益 = (7-1) + (4-3),即奇数位置 - 偶数位置,应该使得这个值最大。
这样两两一组去看,就能把问题转化为最大区间和的问题,利用“当前值 - 最小前缀”和的做法可以优化为 O(N) 求解。
#include <bits/stdc++.h> using namespace std; using ll = long long; void run() { int N; cin >> N; vector<ll> v(N); ll ss = 0; for (int i = 0; i < N; ++i) { cin >> v[i]; if (i % 2 == 0) ss += v[i]; } ll best = ss; ll sum = 0, preMin = 0; for (int i = 0; i + 1 < N; i += 2) { sum += v[i + 1] - v[i]; best = max(best, ss + sum - preMin); preMin = min(preMin, sum); } sum = 0, preMin = 0; for (int i = 1; i + 1 < N; i += 2) { sum += v[i] - v[i + 1]; best = max(best, ss + sum - preMin); preMin = min(preMin, sum); } cout << best << "\n"; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::setvbuf(stdout, nullptr, _IOFBF, BUFSIZ); int T; cin >> T; while (T-- > 0) { run(); } }
E. Sum of Digits
背景:函数 f(x) 的作用是求10进制数字 x 每位的和,给你一个目标值 n 和 k,让你找一个最小的 x 使得 f(x) + f(x+1) + ... + f(x + k) = n.
思路:
先看 k = 0 的情况,即找到最小的 x 使得 f(x) = n
发现 f(19998) = f(9999) = 36,所以 5 位数凑出 36 不如用每位数都为 9 的 4 位数凑。f(59990) = f(49991) = 32,同样都是 5 位数凑 32,应该尽可能让高位小。
因此我们应该有一部分数,而不选择其比他大的数,能够凑出那个 n 来,这些数的特征为:高位为 1-9,低位为 0-9,且中间全是 9,我们把它枚举出来(数量应该不会很多)。
考虑 k 为 1-9 的情况,原来我们枚举的 x 的个位数 0-9 可能不够用了(可能 x + k 发生进位,前面的 9 进没了,加起来就不够了),所以我们枚举 后两位为 80-99,这样就不会进位就够用了。
把这样的可能的 x 都枚举出来,可以发现数量不多,然后从小到大排序后,找到第一个满足 f(x) = n 的即可(暴力)。
#include <bits/stdc++.h> using namespace std; using ll = long long; vector<ll> base; void init() { for (int i = 0; i <= 999; ++i) { // 三位数以下手动处理 base.push_back(i); } for (int mc = 1; mc <= 15; ++mc) { // 中间9的个数 for (int i = 1; i <= 9; ++i) { // 首位数 for (int j = 80; j <= 99; ++j) { // 末尾两位数 string cur = to_string(i) + string(mc, '9') + to_string(j); base.push_back(stoll(cur)); } } } sort(base.begin(), base.end()); } ll f(ll x) { ll ret = 0; while (x > 0) { ret += x % 10; x /= 10; } return ret; } void run() { ll N, K; cin >> N >> K; ll found = -1; for (ll b : base) { ll sum = 0; for (int j = 0; j <= K; ++j) { sum += f(b + j); } if (sum == N) { found = b; break; } } cout << found << endl; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); std::setvbuf(stdout, nullptr, _IOFBF, BUFSIZ); init(); int T; cin >> T; while (T-- > 0) { run(); } }