PROBLEM LINK:
Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4
Author: abdelmohaymn
Tester: yash_daga
Editorialist: iceknight1093
DIFFICULTY:
TBD
PREREQUISITES:
Dynamic programming
PROBLEM:
You’re given a string S that consists of both uppercase and lowercase letters.
The letters \text{a, b, c, \ldots, z} have values \{x, x+1, \ldots, x+25\} in order, and the letters \{A, B, \ldots, Z\} have values \{y, y+1, \ldots, y+25\}.
Let v_c denote the value of character c.
It is guaranteed that |x-y|\geq 26.
You can perform the following operation:
- Choose two characters c_1 and c_2 such that 0 \leq v_{c_1} - v_{c_2} \leq f_{c_1} and f_{c_1} \gt 0.
Here, f_{c_1} denotes the frequency of c_1 in S. - Then, delete all occurrences of c_1 from the string, and append a single copy of c_2 to it.
Find the lexicographically minimum string you can end up with.
EXPLANATION:
Without loss of generality, let x \lt y (so the lowercase letters are ‘smaller’).
Observe that if S contains at least one lowercase letter, it’s possible to delete all of them and end up with several copies of 'a'
.
Similarly, if S contains at least one uppercase letter, it’s possible to delete all of them and end up with several copies of 'A'
.
How?
Note that choosing c_1 = c_2 in our operation is always valid, and replaces every occurrence of c_1 with a single occurrence of it.
Then, we can choose c_1 and c_1 - 1 to shift c_1 to its predecessor, since it has frequency 1 now.
By repeatedly performing this operation, we end up with only 'a'
and 'A'
in S.
This means the answer string only ever needs to contain the characters 'a'
and 'A'
.
Further, observe that:
- If the answer contains only
'a'
, it’s optimal for there to be a single occurrence. - The same applies for when the string contains only
'A'
. - If the answer contains both
'a'
and'A'
, it’s optimal for there to be as many occurrences of'a'
as possible, and exactly one'A'
.
Among these three types of strings, the best is \text{a}, followed by \text{aa\ldots aaA}, and finally \text{A}.
Notice that the last two cases are really just the same thing: a non-zero number of 'a'
s followed by a single 'A'
. So, we club them together into a single case.
Case 1: a
For the final string to be \text{a}, we need to ensure that there are no capital letters in S at all.
That means, if there are any capital letters, we need to be able to transform them all to small ones.
Observe that c_1 can be transformed to c_2 only when 0 \leq v_{c_1} - v_{c_2} \leq f_{c_1}.
Since x\lt y, the 0 \leq v_{c_1} - v_{c_2} part is automatically satisfied when c_1 is capital and c_2 is small; so we only need to worry about the other condition.
v_{c_1} - v_{c_2} \leq f_{c_1} can be rewritten as v_{c_1} - f_{c_1} \leq v_{c_2}.
The LHS is a constant once c_1 is chosen, so our best chance of this equation being satisfied is when v_{c_2} is as large as possible.
Since we’re restricted to small letters, our best hope is to choose c_2 = \text{z}, giving v_{c_2} = x+25.
Now, let \alpha denote some capital letter. Let’s see when it can be transformed into \text{z}.
Certainly, we need v_{\alpha} - f_{\alpha} \leq x+25 to hold.
v_{\alpha} is fixed, so the only thing we can affect is f_{\alpha}.
In particular, note that for each distinct character \beta \gt \alpha present in S, we can get exactly one extra occurrence of \alpha by repeatedly bringing it to a previous character.
To that end, let d_{\alpha} denote the number of distinct elements of S that are \gt \alpha.
Then, \alpha can be converted to \text{z} if and only if v_{\alpha} - (f_{\alpha} + d_{\alpha}) \leq x+25.
Let \alpha_0 be the smallest \alpha for which this condition is true.
Recall that our objective was to ensure that there are no remaining capital letters in S.
So, if \alpha_0 is \leq the smallest capital letter in S, it’s possible to eliminate all capital letters; otherwise it isn’t.
If it is possible, the answer is \text{a}; otherwise move on to the following case.
Case 2: aa...aaA
We’re here because we weren’t able to eliminate every capital letter, so the answer will definitely end with \text{A}.
Our objective now is to have as many occurrences of 'a'
as possible.
First, observe that each occurrence of 'a'
that already exists can remain as-is.
Further, each distinct small letter other than ‘a’ can contribute one occurrence.
That only leaves instances of 'a'
that arise as a result of converting capital letters to small ones.
Once again, observe that it’s always optimal to convert capital letters to 'z'
, so our target is fixed to x+25.
All we need to do is find the maximum number of such conversions - each one can “trickle down” to form a new occurrence of 'a'
.
Notice that this process can be done as follows.
- Try to convert
'Z'
to'z'
, if possible. If we can, great - otherwise, as long as there exists at least one'Z'
, it can be turned into one extra occurrence of a smaller character. - Try the same thing with
'Y'
, except we now (maybe) have one extra occurrence to work with. - Try the same thing with
'X'
, except we now have between 0 and 2 more occurrences to work with.
\vdots
There are several options, and at first glance it’s not clear which of them is optimal (and if a greedy choice will even work).
However, one way to overcome such situations is to just try all options, with the help of dynamic programming!
Let dp_{\alpha, x} denote the maximum number of conversions to 'z'
if we’ve processed all characters \geq \alpha, and there are x ‘extras’ remaining.
This can be computed as follows:
- If \alpha is converted to
'z'
, we need to ensure v_{\alpha} - (x+25) \leq f_{\alpha}.- If this inequality is true, great: any extras remain, so we get dp_{\alpha + 1, x} + 1.
- Otherwise, we need to use some of the extras to increase the frequency of \alpha. Clearly, it’s best to use as few as possible.
So, if y = v_{\alpha} - (x+25) - f_{\alpha}, we transition from dp_{\alpha+1, x+y} + 1.
- If \alpha is not converted to
'z'
, any extras remain as they are; and we might even get one more depending on whether an occurrence of \alpha is present or not.
So, dp_{\alpha, x} transitions from dp_{\alpha+1, x} or dp_{\alpha+1, x-1}.
The maximum number of extra 'a'
we can get is, of course, the maximum of dp_{\text{A}, x} across all x.
The dp only needs 26\times 26 states and \mathcal{O}(1) transitions from each, so it’s plenty fast.
TIME COMPLEXITY:
\mathcal{O}(N + 26^2) per testcase.
CODE:
Tester's code (C++)
#include<bits/stdc++.h>
using namespace std;
#define all(x) begin(x), end(x)
#define sz(x) static_cast<int>((x).size())
#define int long long
const int inf = 1e18;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--) {
int n, x, y;
cin >> n >> x >> y;
string s;
cin >> s;
bool sp = 0;
if (x > y) {
sp = 1;
swap(x, y);
for (auto &c : s) {
int x = (int)c - 'a';
if (x >= 0 && x < 26) c = 'A' + x;
else c = 'a' + x + 32;
}
}
int cA = 0;
bool got[26] = {0};
int cn[26] = {0};
int tot = 0;
for (auto c : s) {
int x = (int)c - 'A';
if (x >= 0 && x < 26) cn[x]++;
}
for (int i = 0; i < 26; i++) {
if (cn[i]) tot++;
}
bool ps = 0, ps2 = 0;
for (int i = 0; i < 26; i++) {
int cA = cn[i];
int req = y - x - 25 + i;
if (cn[i]) tot--;
cA += tot;
if (cA >= req) ps = 1;
if (cn[i]) {
tot++;
break;
}
}
string ans;
if (ps) {
ans.push_back('a');
}
else if (tot) {
string ex;
int yet = 0;
int dp[27];
for (int i = 0; i < 27; i++) {
dp[i] = -inf;
}
dp[0] = 0;
for (int i = 25; i >= 0; i--) {
int req = y - x - 25 + i;
int dif = max(0ll, req - cn[i]);
for (int j = 25; j >= 0; j--) {
int yet = dp[j];
if (yet >= dif) {
dp[j + 1] = max(dp[j + 1], dp[j] - dif);
}
if (cn[i]) dp[j]++;
}
}
int mx = 0;
for (int i = 1; i < 27; i++) {
if (dp[i] >= 0) mx = i;
}
for (int i = 0; i < mx; i++) ex.push_back('a');
ex.push_back('A');
fill(cn, cn + 26, 0);
for (auto c : s) {
int x = (int)c - 'a';
if (x < 26 && x >= 0) cn[x]++;
}
for (int i = 0; i < cn[0]; i++) ans.push_back('a');
for (int i = 1; i < 26; i++) if (cn[i]) ans.push_back('a');
ans += ex;
}
else ans = "a";
if (sp) {
for (auto &c : ans) {
int z = (int)c - 'a';
if (z >= 0 && z < 26) c = 'A' + z;
else c = 'a' + z + 32;
}
}
cout << ans << "\n";
}
}
Editorialist's code (C++)
// #pragma GCC optimize("O3,unroll-loops")
// #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
#include "bits/stdc++.h"
using namespace std;
using ll = long long int;
mt19937_64 rng(chrono::high_resolution_clock::now().time_since_epoch().count());
int main()
{
ios::sync_with_stdio(false); cin.tie(0);
int t; cin >> t;
while (t--) {
int n, x, y; cin >> n >> x >> y;
string s; cin >> s;
vector<int> f1(26), f2(26);
for (auto c : s) {
if (c >= 'a') ++f1[c-'a'];
else ++f2[c-'A'];
}
string prefer = "aA";
if (x > y) {
swap(x, y);
prefer = "Aa";
swap(f1, f2);
}
vector dp(30, vector(30, -1));
// dp[i][j] -> maximum number of shifts to small from characters i..., j 'extra' chars 'after' the shift
dp[26][0] = 0;
for (int i = 25; i >= 0; --i) {
int req = max(y+i - (x+25) - f2[i], 0);
// cerr << i << ' ' << req << '\n';
for (int j = 25; j >= 0; --j) {
if (dp[i+1][j] == -1) continue;
// y+i - x+25 <= f2[i] + extra
// Send to lower
// for (int k = j-req; k >= 0; --k) dp[i][k] = max(dp[i][k], dp[i+1][j] + 1);
if (j >= req) {
dp[i][j-req] = max(dp[i][j-req], dp[i+1][j] + 1);
if (i == 0) dp[i][0] = max(dp[i][0], dp[i+1][j] + 1);
}
// Don't send
if (f2[i] > 0) dp[i][j+1] = max(dp[i][j+1], dp[i+1][j]);
else dp[i][j] = max(dp[i][j], dp[i+1][j]);
}
}
if (dp[0][0] != -1) cout << prefer[0];
else {
int ct = 0;
for (int i = 1; i < 26; ++i) {
if (dp[0][i] != -1) ct = max(ct, dp[0][i]);
}
ct += f1[0];
for (int i = 1; i < 26; ++i) ct += f1[i] > 0;
cout << string(ct, prefer[0]) + string(1, prefer[1]);
}
cout << '\n';
}
}