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();
    }
}

 

posted @ 2020-09-13 18:38  Popco  阅读(282)  评论(0编辑  收藏  举报