源代码:ACM/OpenjudgeNow/Codeforces at master · abmcar/ACM (github.com)

更好的阅读体验: Codeforces Round #787 (Div. 3) 全题解 - Abmcar's 茶水间

Problem - A - Codeforces

大致题意:

image-20220506102234769

思路:

可以知道缺少的狗粮和猫粮分别为

$min(0,a-x)$和$min(0,b-y)$ 判断通用粮是否满足缺口即可

代码:

void solution()
{
    int a, b, c, x, y;
    cin >> a >> b >> c >> x >> y;
    if (min(0LL, a - x) + min(0LL, b - y) + c >= 0)
        cout << "YES" << endl;
    else
        cout << "NO" << endl;
}

Problem - B - Codeforces

大致题意:

image-20220506102450383

思路:

我们知道显然$a_n$不减少是最优的情况

倒叙遍历减少显然是最小操作的情况

当减少到0还不递增时数组无法生成为严格递增

代码:

void solution()
{
    int n;
    cin >> n;
    vector<int> nums(n);
    for (int &it : nums)
        cin >> it;
    int ans = 0;
    int lastNum = 1e12;
    for (int i = n - 1; i >= 0; i--)
    {
        while (nums[i] >= lastNum)
        {
            ans++;
            nums[i] = nums[i] / 2;
            if (nums[i] == 0)
                break;
        }
        if (nums[i] >= lastNum)
        {
            cout << "-1" << endl;
            return;
        }
        lastNum = nums[i];
    }
    cout << ans << endl;
}

Problem - C - Codeforces

大致题意:

image-20220506102756864

思路:

题目有一点绕,但值得注意的是有且仅有一个小偷,我们要做的只是判断出谁可能是小偷

我们可以想一下什么情况下第i个人才可能是小偷?

答案很简单 小偷偷画前的所有人应该见过或者不记得有画,偷画后所有人应该没见到画或者不记得

也就是说,他前面的人没有说画丢了且后面的人都没有说还有画时,这个人才可能偷画

代码:

void solution()
{
    string s;
    cin >> s;
    int n = s.size();
    s = '+' + s;
    vector<bool> aft1(n + 2), pre0(n + 2);
    int ans = 0;
    for (int i = n; i >= 1; i--)
        aft1[i] = aft1[i + 1] or (s[i] == '1');
    for (int i = 1; i <= n; i++)
        pre0[i] = pre0[i - 1] or (s[i] == '0');
    for (int i = 1; i <= n; i++)
        if (!pre0[i - 1] and !aft1[i + 1])
            ans++;
    cout << ans << endl;
}

Problem - D - Codeforces

大致题意:

image-20220506103229875

image-20220506103243929

思路:

不难得知,树有多少个儿子,就有多少条路径

我们只需要从根dfs一遍即可找到路径

因此我们先输出路径数量,即叶子数量

之后dfs(root)

每当dfs到叶子节点时,我们输出当前路径并且清空

代码:

void solution()
{
    int n;
    cin >> n;
    vector<int> father(n + 1), g[n + 1];
    int root = -1;
    for (int i = 1; i <= n; i++)
    {
        cin >> father[i];
        if (i == father[i])
            root = i;
    }
    for (int i = 1; i <= n; i++)
    {
        if (root == i)
            continue;
        g[father[i]].push_back(i);
    }
    int leaf = 0;
    for (int i = 1; i <= n; i++)
        if (g[i].empty())
            leaf++;
    vector<int> ans;
    function<void(int)> dfs = [&](int nowNode) {
        ans.push_back(nowNode);
        // cout << nowNode << endl;
        if (g[nowNode].size() == 0)
        {
            cout << ans.size() << endl;
            for (int it : ans)
                cout << it << " ";
            cout << endl;
            ans.clear();
            return;
        }
        for (int nextNode : g[nowNode])
            dfs(nextNode);
    };
    cout << leaf << endl;
    dfs(root);
    cout << endl;
}

Problem - E - Codeforces

题目大意:

image-20220506103632695

思路:

一个非常有迷惑性的题,k给的相当大

但实际上,由于每次替换的是当前字符串中的所有相同字母,因此只需要'z'-'a'的操作数即可达到最优

我们记录哪些字母已经被替换过了

之后从头开始,每次遇到没被替换过的字母尽可能的替换,之后记录下来本次替换的范围即可

代码:

void solution()
{
    int n, k;
    string s;
    cin >> n >> k >> s;
    map<char, char> mp;
    mp['a'] = 'a';
    for (char &it : s)
    {
        if (mp.count(it))
        {
            it = mp[it];
            continue;
        }
        char oriChar = it;
        int tmp = 0;
        while (k > 0 and !mp.count(it))
        {
            it--;
            k--;
            tmp++;
        }
        char tmpChar = it;
        if (!mp.count(it))
            mp[it] = it;
        it = mp[it];
        while (tmpChar <= oriChar)
        {
            // cout << tmpChar << " " << it << endl;
            mp[tmpChar++] = it;
        }
    }
    cout << s << endl;
}

Problem - F - Codeforces

题目大意:

image-20220506104252726

思路:

(龙龙送外卖)

我们知道所有点都需要去,且最终目的地是y,因此要做的就是尽可能减少来回跑的长度

我们可以先以x为root进行一遍dfs,以vis判断是否来过这个点

之后对于每个任务点$target_i$若该点没有来过,则需要去一趟来一趟,ans+2,标记vis

接着while判断$father[target]$直到$vis[target] == true$

最后 把y也加入任务点 再减去x到y的距离即为最终答案(在y不用返回)

代码:

void solution()
{
    int n, k, x, y;
    cin >> n >> k;
    cin >> x >> y;
    vector<int> g[n + 1], target(k + 1), father(n + 1), deep(n + 1, 1e9);
    vector<bool> vis(n + 1);
    for (int i = 1; i <= k; i++)
        cin >> target[i];
    for (int i = 2; i <= n; i++)
    {
        int t1, t2;
        cin >> t1 >> t2;
        g[t1].push_back(t2);
        g[t2].push_back(t1);
    }
    function<void(int, int)> dfs = [&](int nowNode, int fatherNode) {
        //
        for (int nextNode : g[nowNode])
        {
            if (nextNode == fatherNode)
                continue;
            father[nextNode] = nowNode;
            deep[nextNode] = deep[nowNode] + 1;
            dfs(nextNode, nowNode);
        }
    };
    deep[x] = 0;
    dfs(x, -1);
    vis[x] = true;
    int ans = 0;
    target[0] = y;
    for (int targetNode : target)
    {
        while (!vis[targetNode])
        {
            ans += 2;
            vis[targetNode] = true;
            targetNode = father[targetNode];
        }
    }
    ans = ans - deep[y];
    cout << ans << endl;
}

Problem - G - Codeforces

题目大意:

image-20220506105317804

代码:

int main()
{
    int n, m;
    cin >> n >> m;
    vector<int> nums(n + 1), preAns(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> nums[i];
    for (int i = 1; i <= n; i++)
        preAns[i] = preAns[i - 1] + nums[i];
    vector dp(n + 1, vector<int>(m + 1, 1e9));
    // dp[i][j] -> 前i个位置用了j个盘子之后达成递增的最小操作数
    // dp[i][j] = min{dp[i-1][j-k] + abs(j-k-preAns[i-1])}
    dp[0][0] = 0;
    // 下一个位置放的盘子数
    for (int nextValue = m; nextValue >= 0; nextValue--)
        // 当前已经用的盘子数
        for (int nowValue = 0; nowValue <= m - nextValue; nowValue++)
            // 当前位置
            for (int i = 1; i <= n; i++)
                /*  前面i-1个已经是递增的 代价已经在dp[i - 1][nowValue]里面了
                 abs算的是前i - 1个整体少的或者多的部分需要从i这里过的操作数 */
                dp[i][nextValue + nowValue] =
                    min(dp[i][nextValue + nowValue], dp[i - 1][nowValue] + abs(nowValue - preAns[i - 1]));
    cout << dp[n][m] << endl;
    return 0;
}

Q.E.D.