Codeforces Round 1054 (Div. 3)
https://codeforces.com/contest/2149
A. Be Positive
题意:给定一个只含-1,0,1的数组,每次可以选择任意一个元素增加1,使得最后整个数组的乘积为正数,需要的最少操作数。
题解:对于任何一个0,都需要操作一次,使其变成1,对于偶数个-1不需要操作,奇数个-1需要将其变成1,操作2次。
void slove()
{
int n; cin >> n;
vector<int> a(n + 5);
int cnt0 = 0, cnt1 = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] == 0) {
cnt0++;
} else if (a[i] == -1) {
cnt1++;
}
}
int res = cnt0;
if (cnt1 & 1) res += 2;
cout << res << endl;
}
B. Unconventional Pairs
题意:给定一个数组,数组长度为偶数。可以任意分成 n 个二元组,最小化所有二元组之差的绝对值的最大值。
题解:求二元组之差的绝对值的最大值,要求这个最大值最小,该如何构造这些二元组。升序排序即可。
void slove()
{
int n; cin >> n;
vector<int> a(n + 5);
for (int i = 1; i <= n; i++) cin >> a[i];
sort(a.begin() + 1, a.begin() + 1 + n);
int res = 0;
for (int i = 1; i <= n - 1; i++) {
res = max(res, abs(a[i + 1] - a[i]));
i++;
}
cout << res << endl;
}
C. MEX rose
题意:给定一个数组 a,每次可以修改任何一个元素,使其变成 [1,n] 之间的任何一个数,要求 mex(a) = k,最少操作数。
题解:首先求出原数组的 mex 为 cur,那么对于 cur 之前的元素都是存在的,考虑 k > cur。[cur,k - 1] 之间需要填满,k这个元素需要全部修改为其它元素,对于这两个部分的操作取最大值即可,因为k这个元素可以修改为 [cur, k - 1] 之间的元素。如果 k < cur ,那么就是 k的元素个数。
void slove()
{
int n, k; cin >> n >> k;
vector<int> a(n + 5), cnt(n + 5);
for (int i = 1; i <= n; i++) {
cin >> a[i];
cnt[a[i]]++;
}
sort(a.begin() + 1, a.begin() + 1 + n);
int cur = -1;
for (int i = 0; i <= n; i++) {
if (cnt[i] == 0) {
cur = i;
break;
}
}
int res = 0;
if (cur == k) {
cout << res << endl;
return;
}
for (int i = cur; i < k; i++) { // if k < cur => res = 0. 取max也是成立的
if (cnt[i] == 0) {
res++;
}
}
res = max(res, cnt[k]);
cout << res << endl;
}
D. A and B
题意:给定一个只包含 'a', 'b' 的字符串,每次可以交换两个相邻位置的字符。使得字符串变成存在一个字符全部是连续的。例如 "aabbbbaaa" , "baaaaabbbb"
题解:对于 'a','b' 两种字符,可以分别求将一种字符放在两边的最小操作,然后取min即可。
int n;
string s;
// 将字符 b 放在两边的最小操作数
LL cal(char a, char b) {
vector<int> cnt_pre(n + 5), cnt_aft(n + 5); // b 元素个数的前缀和以及后缀和
cnt_pre[0] = s[0] == b ? 1 : 0;
for (int i = 1; i < n; i++) {
cnt_pre[i] = cnt_pre[i - 1] + (s[i] == b);
}
for (int i = n - 1; i >= 0; i--) {
cnt_aft[i] = cnt_aft[i + 1] + (s[i] == b);
}
LL res = 0;
for (int i = 0; i < n; i++) {
if (s[i] != b) continue; // 只操作 b
// 将 b 放在左边和右边的最小操作数,即前缀a的个数和后缀a的个数。
// 任意一个b,和左边/右边的a依次交换
res += min(i + 1 - cnt_pre[i], n - 1 - i + 1 - cnt_aft[i]); // +1 表示该位置为 b
}
return res;
}
void slove()
{
cin >> n;
cin >> s;
LL res1 = cal('a', 'b');
LL res2 = cal('b', 'a');
cout << min(res1, res2) << endl;
}
E. Hidden Knowledge of the Ancients
题意:求子数组恰好有k种元素,长度在[l, r]之间的数量。
题解:滑动窗口,固定左端点,分别求右端点的最大值和最小值。相减即为左端点的贡献。
考虑k种元素,此刻恰好为k种元素,即为右端点的最小值,还需要和长度为 l 取max。
当为 k + 1 种元素时,上一个元素的位置就是为k种元素的右端点的最大值。
void slove()
{
int n, k, l, r; cin >> n >> k >> l >> r;
vector<int> a(n + 5);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
multiset<int> S;
int cnt = 0;
vector<int> p1(n + 5), p2(n + 5); // p1[i] 表示左端点为i,右端点的最小值,p2 为最大值
for (int i = 1, j = 1; i <= n; i++) {
while (j <= n && cnt < k && j - i + 1 <= r) {
if (S.find(a[j]) == S.end()) {
cnt++;
}
S.insert(a[j]);
j++;
}
if (cnt == k) {
p1[i] = j - 1;
p1[i] = max(p1[i], i + l - 1); // 考虑区间长度的最小值
} else {
p1[i] = -1; // 没有满足条件的子数组,贡献为 0
}
S.erase(S.find(a[i]));
if (S.find(a[i]) == S.end()) {
cnt--;
}
}
k++;
for (int i = 1, j = 1; i <= n; i++) {
while (j <= n && cnt < k && j - i + 1 <= r) {
if (S.find(a[j]) == S.end()) {
cnt++;
}
S.insert(a[j]);
j++;
}
if (cnt == k) {
p2[i] = j - 1;
} else {
p2[i] = j;
}
S.erase(S.find(a[i]));
if (S.find(a[i]) == S.end()) {
cnt--;
}
}
LL res = 0;
for (int i = 1; i <= n; i++) {
if (p1[i] == -1 || p1[i] >= p2[i]) continue;
res += p2[i] - p1[i];
}
cout << res << endl;
}
F. Nezuko in the Clearing
题意:一个人有初始血量 h ,需要走到 d 处。每次运动走一步,在连续运动时,消耗血量也是连续的。第一次消耗1点,第二次消耗2点....... 休息一次可以增加 1 血量。任意时刻血量都需要大于 0. 从 0 点开始运行,到 d 需要最少的回合。
题意:考虑休息是在同一点,还是分开更优。两种方案增加的血量是相同的,但是分开所消耗的血量更少。所以所有的休息是同一点最多一次。那么每次休息就可以使得运动重新开始(消耗血量再次从 1 开始)。
设休息 x 次,那么可以将整个路段分为 x + 1 段,一共需要走 d 步,分 x + 1 次走,每步的步长为 d / (x + 1),设为 len ,
所消耗的血量为 len * (len + 1) / 2 ,由于是向下取整,对于 r = d % (x + 1) 这些路段,步长为 len + 1。
所以可以计算出休息 x 次,所消耗的血量,和增加的血量。休息次数越多,消耗的血量越少,存在二段性。
int h, d;
bool check(int x) {
int r = d % (x + 1), a = d / (x + 1);
int b = a + 1;
int cnt = x + 1 - r;
LL sum = 1LL * cnt * a * (a + 1) / 2 + 1LL * r * b * (b + 1) / 2;
return sum < h + x; // 任意时刻血量都需要大于 0
}
void slove()
{
cin >> h >> d;
int l = 0, r = d;
while (l < r) {
int mid = l + r - 1 >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l + d << endl;
}
浙公网安备 33010602011771号