比赛地址: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 — 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;
}