Topics
- Input: A string (garland) of length
n
, where each position has a color - Queries: Each query gives:
- A number
m
indicating the maximum pieces you can repaint - A target color
c
(Koyomi’s favorite)
- A number
- Goal: For each query, determine the maximum length of a contiguous segment (subsegment) that can be made entirely of color
c
by repainting at mostm
pieces
Idea
Key Observations
- If a segment already has many pieces of color
c
, you need fewer repaints to convert the whole segment, e.g:komodoo
with 2 repaints →koooooo
- You want to balance the window size (segment length) with the repainting limit
m
such that the number of non-c
pieces in the window does not exceedm
Approach 1 (Sliding Window)
Use two pointers (l
and r
) to maintain a window. Increase r
(expanding the window) until you would need more than m
repaints (i.e., the count of pieces not equal to c
exceeds m
). Then, update the maximum length (aka Koyomity) and move l
to reduce the repaint cost.
- Time Complexity: per query, so overall for all queries
- Code Mechanics: The inner loop increments
r
while decreasingmax_repaint
when a non-c
is encountered, and the outer loop shiftsl
while “restoring” a repaint opportunity if the left element was notc
(typical sliding window)
Approach 2 (Sliding Window + Prefix Sums)
Precompute prefix sums for each color, so you can quickly calculate how many occurrences of the target color exist in any subsegment. Then use a sliding window to check if a segment can be made uniform with at most m
repaints. Optimization over previous approach.
- Time Complexity:
- Code Mechanics: It calculates
c_count
(number of target color pieces in the current window) and compares it with the window length plus available repaints to decide if the window is valid
Approach 3 (Dynamic Programming)
Use a 3D DP array where dp[i][j][k]
represents the best (maximum) subsegment length achievable considering the first i
pieces, having used j
repaints, and focusing on the target color k
.
- DP Logic When adding a new character, you either:
- Repaint it (using one repaint) if it’s not
c
. - Or if it’s already
c
, you can extend the segment without using a repaint.
- Repaint it (using one repaint) if it’s not
- Time Complexity:
This approach is more heavy-weight (around ), so it’s suitable when you need to precompute answers for a large number of queries. - Code Mechanics:
It carefully updates the DP states considering both painting and saving repaints when the character matches the target.
Each approach has its trade-offs:
- The sliding window methods are direct and efficient for a moderate number of queries.
- The DP method is more precomputation-heavy but can answer many queries quickly once the table is built.
Tip
Few other approaches/optimizations:
- Using binary search on ans for each query (to find max segment) and checking each candidate ans via sliding window
- Since we only have 26 chars and m ⇐ n, thus we can have atmost
26*n
unique queries, i.e. many queries will be duplicated ⇒ cache the answers!!
Code
// Approach 1 (sliding window)
void solve() {
int n;
cin >> n;
string s;
cin >> s;
int q;
int m, l, r, runner, best;
char c;
cin >> q;
while (q--) {
cin >> m >> c;
l = r = 0;
best = runner = 0;
for (; r < n; r++) {
runner += (s[r] != c);
// shrink from left
while (runner > m) {
runner -= s[l] != c;
l++;
}
best = max(best, r - l + 1);
}
cout << best << '\n';
}
}
// Approach 2 (Sliding window + Precomputation)
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = "#" + s;
// precomputation
vector<vector<int>> dp(26, vector<int>(n + 1, 0));
for (int i = 0; i < 26; ++i) {
for (int j = 1; j <= n; ++j) {
dp[i][j] = (s[j] == 'a' + i) + dp[i][j - 1];
}
}
int q;
int m, l, r, count_c, best;
char c;
cin >> q;
while (q--) {
cin >> m >> c;
l = r = 1;
best = count_c = 0;
for (; r <= n; ++r) {
count_c = dp[c - 'a'][r] - dp[c - 'a'][l - 1];
// shrink from left
while (count_c + m < r - l + 1) {
l++;
count_c = dp[c - 'a'][r] - dp[c - 'a'][l - 1];
}
best = max(best, r - l + 1);
}
cout << best << '\n';
}
}
// Approach 3 (DP)
void solve() {
int n;
string s;
cin >> n >> s;
// dp[color][repaints][position] represents the longest sequence ending at
// position after using exactly 'repaints' changes to make color sequences
vector<vector<vector<int>>> dp(26,
vector<vector<int>>(n + 1, vector<int>(n)));
vector<vector<int>> maxLen(
26, vector<int>(n + 1)); // Store maximum achievable lengths
for (int color = 0; color < 26; ++color) {
char targetColor = 'a' + color;
// Base case: no repaints allowed
for (int pos = 0; pos < n; ++pos) {
dp[color][0][pos] = (s[pos] == targetColor)
? (pos > 0 ? dp[color][0][pos - 1] : 0) + 1
: 0;
maxLen[color][0] = max(maxLen[color][0], dp[color][0][pos]);
}
// For each number of allowed repaints
for (int repaints = 1; repaints <= n; ++repaints) {
for (int pos = 0; pos < n; ++pos) {
if (s[pos] == targetColor) {
// No need to repaint this position
dp[color][repaints][pos] =
(pos > 0 ? dp[color][repaints][pos - 1] : 0) + 1;
} else {
// Either repaint this position or take previous best
dp[color][repaints][pos] =
(pos > 0 ? dp[color][repaints - 1][pos - 1] : 0) + 1;
}
maxLen[color][repaints] =
max(maxLen[color][repaints], dp[color][repaints][pos]);
}
}
}
int q;
cin >> q;
while (q--) {
int m;
char c;
cin >> m >> c;
cout << maxLen[c - 'a'][m] << '\n';
}
}