Codeforces Round #570 (Div. 3) 解题报告

比赛地址:Codeforces Round #570 (Div. 3)

A. Nearest Interesting Number

题目大意:定义一个数为有趣的,当且仅当其各个数位的数之和为 4 的倍数,给定一个数 n ,找出比它大的最小的有趣的数

解题思路:暴力模拟即可

#include <cstdio>

int n;

int get_sum(int key) {
    int tot = 0;
    while(key) {
        tot += (key % 10);
        key /= 10;
    }
    return tot;
}

int main() {
    scanf("%d", &n);
    while(get_sum(n) % 4 != 0) {
        ++n;
    }
    printf("%d", n);
    return 0;
}

B. Equalize Prices

题目大意:给定一个序列,对这个序列中的每一个数 \(a_i\) ,将其变成另一个数 \(b_i\) ,要求 \(| a_i - b_i | \le k\) ,问可不可以通过这样的操作是这个序列中所有的元素全部相等,可以输则出最大的相等的值,不可以则输出 -1 。

解题思路:因为最终要使得所有的数都相等,所以尽量小的数加,大的数减。所以很快得到有解的条件:\(max &mdash; min < 2k\) 。而在这个前提下,为了最大化相等的值,我们不妨就让最小值加上 \(k\) ,很明显,这个值其他的数经过变换也可以达到,而且更大数就不能满足题设条件了,因为最小的数加上 \(k\) 也不能达到,所以 \(min + k\) 是有解情况下的最大的答案。

#include <cstdio>
#include <iostream>

const int MAXN = 105;

int q, n, k, min, max, arr;

int main() {
    scanf("%d", &q);
    while (q--) {
        scanf("%d%d", &n, &k);
        min = 1e8 + 1;
        max = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &arr);
            min = std::min(min, arr);
            max = std::max(max, arr);
        }
        if (max - min > (k << 1)) printf("-1\n");
        else printf("%d\n", min + k);
    }
    return 0;
}

C. Computer Game

题目大意:电脑最开始有 \(k\) 的电量,要玩 \(n\) 次游戏,要么直接玩,要么边充电边玩,直接玩一次游戏电量下降 \(a\) ,边充电边玩一次游戏电量下降 \(b\) ,已知 \(a > b\) ,要求进行完 \(n\) 次游戏的过程中,电量恒大于 0 ,最大化直接玩游戏的次数,无解输出 -1 。

解题思路:对于这个想玩游戏的人,最稳的思路就是一直边充电边玩,如果这样也阻止不了电量到 0 ,那么也没有什么别的办法了(无解),否则,多余的电量就可以用来浪(不充电一直玩),我们可以把多余的电量拿出尽可能多的 \(a - b\) 去放到尽可能多的一次游戏中去,直到多余的电量不足以分配或者不能够再分配(所有的 \(b\) 都变成了 \(a\))为止,分配的次数就是答案,同时,注意到题目的限制:电量恒大于 0 ,因此在分配之前要先抽出 1 的 电量防止题目的限制被打破。

值得注意的是:如果采取乘法的方式来判断解的存在性,注意到数据范围,乘法有可能爆 int ,所以要开 long long 。

#include <cstdio>

typedef long long int ll;

ll q, k, n, a, b;

int main() {
    scanf("%d", &q);
    while(q--) {
        scanf("%d%d%d%d", &k, &n, &a, &b);
        if (k <= n * b) {
            printf("-1\n");
        }
        else if (k > n * a) {
            printf("%lld\n", n);
        }
        else {
            k -= n*b;
            k = (k - 1) / (a - b);
            printf("%lld\n", k);
        }
    }
    return 0;
}

D. Candy Box (easy version)

题目大意:有 \(n\) 个糖果,每个糖果都有一个类型,要求选出尽可能多的糖果组成一个礼物,其中不同类型的糖果的数量不相同(但是可以均为 0 )。求糖果数量的最大值。

解题思路:先统计出每一种类型的糖果的数量,从大到小排序后构造出一个尽可能大的最长下降序列即可。

值得注意的是:由于本题多组数据,且数据范围较大,要慎用 memset ,其复杂度为 \(\Theta (n)\) ,如果进行 \(n\) 次就会变成 \(\Theta (n^2)\) ,这样就会导致 TLE ,所以初始化的方式需要慎重考虑。

#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long int ll;
const int MAXN = 2e5 + 5;
int q, n, candy;
ll ans;
int num[MAXN];
bool CMP(int a, int b) {
    return a > b;
}
int main() {
    scanf("%d", &q);
    while (q--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) num[i] = 0;
        ans = 0;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &candy);
            ++num[candy];
        }
        std::sort(num + 1, num + n + 1, CMP);
        ll temp = num[1] + 1;
        for (int i = 1; (i <= n) && (num[i] != 0) && (temp != 0); ++i) {
            temp = std::min(temp - 1, (ll) num[i]);
            ans += temp;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

E. Subsequences (easy version)

题目大意:给你一个字符序列,你可以删去其中若干个字符得到一个子序列,代价是删去的字符数,要求得到一个含有 \(k\) 个子序列的集合,最小化达到这一目的的代价,或者输出无解。

解题思路:因为要最小化代价,所以我们应该从删去 0 个、1 个、2个开始考虑。注意到本题的数据范围很小,所以可以直接 \(BFS\) 。暴力枚举删去的字符,相同的子序列使用 set 来判重,直到得到了 \(k\) 个子序列,或者确定无解为止。

值得注意的是:\(k\) 有可能为 1 ,所以判断子序列的个数应该在循环的最开始(一开始就已经满足了条件)再枚举删去的字符,否则就会导致无限循环从而 TLE 。

#include <cstdio>
#include <iostream>
#include <string>
#include <queue>
#include <set>

using namespace std;

const int MAXN = 105;

int n, k, l, cnt, ans;
string str;

queue<string> q;
set<string> s;

int main() {
    scanf("%d%lld", &n, &k);
    cin >> str;
    l = str.length();
    s.insert(str);
    cnt = 1;
    q.push(str);
    while ((!q.empty()) && (cnt < k)) {
        string now = q.front(), cpnow = now;
        q.pop();
        int len = now.length();
        for (int i = 0; i < len; ++i) {
            now.erase(i, 1);
            if (!s.count(now)) {
                q.push(now);
                s.insert(now);
                ++cnt;
                ans += (l - len + 1);
                if (cnt == k) break;
            }
            now = cpnow;
        }
    }
    if (cnt == k) printf("%d", ans);
    else printf("-1");
    return 0;
}

F. Topforces Strikes Back

题目大意:给你 \(n\) 个正整数,从中选出最多三个数,使得这些数互不被对方整除,最大化选出的数的和。

解题思路:我们首先注意到我们绝对不会选两个相同的数,因此我们可以先对给定的 \(n\) 个数去重,去重完后最直接的想法就是直接从大往小枚举这三个数,及先保证第一个数尽可能大,在保证第 \(i\) 个数尽可能大的前提下保证第 \(i + 1\) 个数尽可能大,找到第一对就直接退出,把找到的结果作为答案。

先来分析这个算法的正确性:如果选一个数,肯定选最大的数。如果选两个数,唯一能够卡掉这个思路的数据是 \(a < b < c < d\) ,且满足 \((a,d)\)\((b,c)\) 均为满足条件的数对且 \(b + c > a + d\) ,在这种情况下,我们的算法找到的是 \((a,d)\) 。但是这种情况是不存在的,因为 \((c,d)\) 为不满足条件的数对,所以必然有 \(c | d\) ,因此 \(c < \frac{d}{2}\) ,所以 \(b + c < 2c < d < a + d\)。如果选三个数,同理可证我们的算法是正确的。

接下来分析这个算法的时间复杂度,因为有三层循环,所以在最坏情况下,其复杂度为 \(\Theta(n^3)\) ,然而最坏情况为这个序列中的第 \(i + 1\) 个元素总能被第 \(i\) 个元素整除,因为每个元素的值有上限,那么要使得这个序列尽可能长,那么就是公比为 2 的等差数列(注意我们已经对原数列进行了去重,数列中没有重复的元素),但这也就注定了这个序列不能够很长,我们的经验告诉我们: \(\Theta(n^3)\) 的复杂度可以过 \(n = 100\) 的数据,但是 \(2^{100}\) 已经是一个天文数字了,远远超出了题目给的上限。因此事实上我们的算法是跑不满的,它可以很好地在给定的时间范围内解决我们的问题。

#include <cstdio>
#include <algorithm>

const int MAXN = 2e5 + 5;

int q, n, cnt, ans;
int arr[MAXN];

int main() {
    scanf("%d", &q);
    while(q--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &arr[i]);
        }
        std::sort(arr + 1, arr + n + 1);
        cnt = std::unique(arr + 1, arr + n + 1) - (arr + 1);
        ans = arr[cnt];
        for (int i = cnt; i; --i) {
            for (int j = i - 1; j; --j) {
                if (arr[i] % arr[j] == 0) continue;
                ans = std::max(ans, arr[i] + arr[j]);
                for (int k = j - 1; k; --k) {
                    if ((arr[i] % arr[k] == 0) || (arr[j] % arr[k] == 0)) continue;
                    ans = std::max(ans, arr[i] + arr[j] + arr[k]);
                    break;
                }
                break;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

G. Candy Box (hard version)

题目大意:有 \(n\) 个糖果,每个糖果都有一个类型,同时有一个参数(为 \(0\)\(1\)),要求选出尽可能多的糖果组成一个礼物,其中不同类型的糖果的数量不相同(但是可以均为 0 )。求糖果数量的最大值,并在此基础上最大化参数为 \(1\) 的糖果的数量。

解题思路:第一问和 D 题思路一致。对于第二问,考虑贪心,对于选定的某一数量的糖果,在糖果总数量大于等于它的所有糖果中选择参数为 \(1\) 的糖果数量最多的糖果,一直处理到最后就可以获得最大的参数为 \(1\) 的糖果的数量,而这个过程可以使用优先队列来完成。

#include <cstdio>
#include <algorithm>
#include <queue>
 
const int MAXN = 2e5 + 1;
 
int q, n, a, f, place;
int res[MAXN], ans[2];
 
std::priority_queue<int> sum;
 
struct node {
    int num, cnt;
    node(int nn = 0, int cc = 0) {
        num = nn;
        cnt = cc;
    }
}candy[MAXN];
 
bool cmp(const node &x, const node &y) {
    return x.num > y.num;
}
 
void ini(int key) {
    for (int i = 1; i <= key; ++i) {
        candy[i].num = 0;
        candy[i].cnt = 0;
        res[i] = 0;
    }
    ans[0] = ans[1] = 0;
    while (!sum.empty()) {
        sum.pop();
    }
    place = 1;
}
 
int main() {
    //freopen("test.in", "r", stdin); 
    scanf("%d", &q);
    while (q--) {
        scanf("%d", &n);
        ini(n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d", &a, &f);
            ++candy[a].num;
            if (f == 1) {
                ++ candy[a].cnt;
            }
        }
        std::sort(candy + 1, candy + n + 1, cmp);
        res[0] = candy[1].num + 1;
        for (int i = 1;(i <= n) && (candy[i].num != 0) && (res[i - 1] != 0); ++i) {
            res[i] = std::min(res[i - 1] - 1, candy[i].num);
            ans[0] += res[i];
        }
        for (int i = 1;(i <= n) && (res[i] != 0); ++i) {
            for (;(place <= n) && (candy[place].num >= res[i]); ++place) {
                sum.push(candy[place].cnt);
            }
            ans[1] += std::min(sum.top(), res[i]);
            sum.pop(); 
        }
        printf("%d %d\n", ans[0], ans[1]);
    }
    return 0;
}

H. Subsequences (hard version)

题目大意:给你一个字符序列,你可以删去其中若干个字符得到一个子序列,代价是删去的字符数,要求得到一个含有 \(k\) 个子序列的集合,最小化达到这一目的的代价,或者输出无解,由于数据范围加大, BFS 稳 T 不赔。

解题思路:这个时候我们就考虑 dp ,我们假设 \(dp[i][j]\) 表示考虑到第 \(i\) 个位置,删除了 \(j\) 个字母的方案数,那么首先有 \(dp[i][0] = 1\) 。现在考虑转移条件,对于某一个位置的字母,我们要么删去它,要么不删去它,因此就有 \(dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]\) 。还有最后一种情况,就是我们删除了不同位置的字母后,得到了相同的字符串。这种情况显然只会发生在两个相同的字母之间,先考虑相邻(这两个字母之间不存在另一个位置的字母与这两个字母都相同)的两个相同的字母,这个时候,设第 \(i\) 个字母的前一个相同字母的位置为 \(last[i]\) ,那么我们只需令 \(dp[i][j] = dp[i][j] - dp[last[i] - 1][j - (i - last[i] + 1) + 1]\) 就可以减去相同的情况了,因为对于这两个相同的字母,得到本质相同的字符串必须删除它们中间的那些字符,使得两个字符相邻,在这种情况下,删除两个字符中的任何一个字符才都是等价的。现在我们来考虑要不要考虑不相邻的相同的字母,我们记那个字母的位置为 \(place[i] (place[i] < last[i])\) ,根据我们的转移方程, \(dp[last[i] - 1][j - (i - last[i] + 1) + 1]\) 里的情况数是包含了 \(dp[place[i] - 1][j - (i - place[i] + 1) + 1]\) 里的情况数,所以不需要再额外考虑。

在经历上述 dp 后,我们就得到了删除原串中 \(i\) 个字母本质不同的方案数 \(dp[n][i]\) ,这个时候我们再对 \(i\) 从小到大暴力统计 \(dp[n][i]\)\(k\) ,同时记录代价即可,这样得到的代价一定是最小的,如果 \(dp[n][i]\) 统计完了还到不了 \(k\) ,那么说明无解。

#include <cstdio>
#include <algorithm>
 
typedef long long int ll;
 
const int MAXN = 105;
 
int n;
int last[MAXN];
ll k, cnt, ans;
ll dp[MAXN][MAXN];
char str[MAXN];
 
int main() {
    scanf("%d%lld%s", &n, &k, str + 1);
    dp[0][0] = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= i; ++j) {
            if (j == 0)
                dp[i][j] = 1;
            else 
                dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
            int tmp = str[i] - 'a';
            if (last[tmp] && (j >= i - last[tmp]))
                dp[i][j] -= dp[last[tmp] - 1][j - (i - last[tmp])];
        }
        last[str[i] - 'a'] = i;
    }
    for (int i = 0; i <= n; ++i) {
        ans += std::min(k - cnt, dp[n][i]) * i;
        cnt += std::min(k - cnt, dp[n][i]);
        if (cnt == k)
            break;
    }
    if (cnt == k)
        printf("%lld", ans);
    else
        printf("-1");
    return 0;
}
posted @ 2019-07-04 10:58  lornd  阅读(309)  评论(0编辑  收藏  举报