String processing patterns
common
The two Senior S2 problems below need no string algorithm. They need three small patterns, applied cleanly: a frequency table over the letters, a parser for a compact encoding, and modular arithmetic to index into a pattern that repeats forever. This module builds each pattern on its own, then applies them to the two problems.
Counting letter frequencies
Many string problems ask a question about letters: which letters repeat, how often each appears, whether two neighbours share a property. Answering any of these starts with a frequency table: one counter per letter, filled in a single pass.
For lowercase strings, a length-26 array indexed by letter - 'a' is enough.
freq[c] is the number of occurrences of letter c in the string.
int freq[26]={};
for(char ch:s){
freq[ch-'a']++;
}Building the table costs one pass over the string. Every question afterwards reads the table instead of rescanning the string, so a problem about a string of length N becomes a problem about 26 numbers.
The property being tracked is whatever the problem defines. In Heavy-Light Composition below, a letter is heavy when its count is greater than 1 and light otherwise, which takes two extra lines on top of the table.
Parsing compact encodings
Problems frequently hand you a string in a compressed form and expect you to reconstruct or reason about the original. The common encoding is Run-Length Encoding (RLE): a character followed by the number of times it repeats, as in a4b1c2d10 for aaaabccdddddddddd. The Strings module covers the encoding side with Cold Compress. Here we go the other way.
Parsing is a two-level scan: read one letter, then read the digit run that follows it. The digit run matters: counts can be multi-digit, so accumulate digits rather than taking a single character.
The parsed result is a list of (character, count) runs. Everything a problem asks about the decoded pattern, such as its total length, which character sits at a position, or how runs merge, can be answered from this list without ever materializing the full string.
vector<pair<char,long long>> runs; // (character, count)
int i=0;
while(i<(int)s.size()){
char ch=s[i];
i++;
long long num=0;
while(i<(int)s.size() && isdigit(s[i])){
num=num*10+(s[i]-'0');
i++;
}
runs.push_back({ch,num});
}The counts can reach , and that has a language-specific consequence. In C++, store every count and any running total over them as long long: an int overflows near , and the overflow is silent, so the program keeps running with garbage counts. Python integers have no size limit, so nothing special is needed.
Indexing into repeated patterns
When a pattern of length L repeats forever, position c of the infinite string lives inside a single copy:
The character at position c of the infinite repetition is the character at position of one copy.
Positions count from 0 here. For positions counted from 1, use .
This one line turns an unbounded index into a bounded one. To find which run covers that position, sweep the runs in order and subtract each count. The run where the counter first goes negative is the answer. The sweep visits every run at most once, so the whole query is linear in the length of the encoding.
long long length=0;
for(auto& run:runs){
length+=run.second;
}
c%=length; // reduce the index to one copy
for(auto& run:runs){
if(c<run.second){
cout<<run.first<<"\n";
break;
}
c-=run.second;
}S2 Walkthrough: Heavy-Light Composition
Heavy-Light Composition2024 · S2 · Original problem · DMOJ
In a string containing only lowercase letters of the alphabet ("a" through "z"), we say a letter is heavy if it appears more than once in the string, and light otherwise.
We will be given a number of strings. For each string, we would like to determine whether the letters of the string alternate between light and heavy.
The first line of input will consist of two positive integers and , representing the number of strings and the length of each string.
The next lines each contain a sequence of lowercase letters of the alphabet.
The following table shows how the available marks are distributed:
| Marks | Bounds on | Bounds on | Other Restrictions |
|---|---|---|---|
| Only the letters "a" and "b" will be used | |||
| None | |||
| Only the letter "a" will be heavy; all other letters are light | |||
| None |
Output lines, where each line will be either T or F. If the -th input string does alternate between light and heavy letters, the -th line of output should be T; and otherwise, the -th line of output should be F.
3 4
abcb
bcbb
babc Output for Sample Input 1
T
F
T Explanation of Output for Sample Input 1
The first string is composed of a light letter, then a heavy letter, then a light letter, and then a heavy letter.
The second string ends in two consecutive heavy letters.
The third string is composed of a heavy letter, then a light letter, then a heavy letter, and then a light letter.
2 3
abc
bcb Output for Sample Input 2
F
T Explanation of Output for Sample Input 2
The first string is composed of all light letters.
The second string is composed of a heavy letter, then a light letter, and then a heavy letter.
Solution
Each string is independent, so we solve one string and repeat for all T of them.
First, build the frequency table for the string. A letter is heavy exactly when its count exceeds 1:
The alternation condition then says every adjacent pair must differ in this property: a light letter next to a heavy letter, then light again, and so on. Scanning the string once and comparing neighbouring letters against the table decides it:
If every pair satisfies this, print T. Otherwise print F. Building the table is per string and the scan is , so the total is .
Implementation
#include<bits/stdc++.h>
using namespace std;
int main(){
int t,n;
cin>>t>>n;
while(t--){
string s;
cin>>s;
int freq[26]={};
for(char ch:s){
freq[ch-'a']++;
}
bool ok=true;
for(int i=0;i+1<(int)s.size();i++){
bool heavy1=freq[s[i]-'a']>1;
bool heavy2=freq[s[i+1]-'a']>1;
if(heavy1==heavy2){
ok=false;
}
}
cout<<(ok?"T":"F")<<"\n";
}
}S2 Walkthrough: Cryptogram Cracking Club
Cryptogram Cracking Club2025 · S2 · Original problem · DMOJ
Cyrene, the captain of the Cryptogram Cracking Club (CCC), came across a concerningly long cipher. Conveniently, this cipher is composed of lower-case characters (a-z). Comfortingly, the cipher is composed of a pattern that repeats infinitely.
Cyrene wishes to locate the -th character of the cipher. To make your job easier, the CCC members have extracted the repeated pattern and compressed it using the Run-Length Encoding (RLE) algorithm, which replaces consecutive repeated characters with a single occurrence of the character followed by a count of how many times it was repeated. For example, for the pattern aaaabccdddd, the RLE algorithm outputs a4b1c2d4.
You are given the output of the RLE algorithm for a certain pattern. Can you determine the -th character of the long cipher that is formed by repeating this pattern infinitely?
The first line of input will consist of a string , representing a pattern produced by the RLE algorithm. The length of will be at least and at most . Additionally, all numbers appearing in are between and .
The next line of input contains a single integer , representing the index of the character you wish to locate, starting from index .
The following table shows how the available marks are distributed:
| Marks | Bounds on | Additional Constraints |
|---|---|---|
| All numbers appearing in are between and (inclusive) and the length of the repeated pattern is at most characters. | ||
| The length of the repeated pattern is at most characters. | ||
| The length of the repeated pattern is at most characters. | ||
| No additional constraints. |
Output the -th character of the long cipher.
r2d2
8Output for Sample Input 1
r
Explanation of Output for Sample Input 1
The output of the RLE algorithm r2d2 corresponds to the pattern rrdd, which creates the infinitely long cipher rrddrrddrrddrrdd..., where the th character is r.
a4b1c2d10
100Output for Sample Input 2
d
Explanation of Output for Sample Input 2
The output of the RLE algorithm a4b1c2d10 corresponds to the pattern aaaabccdddddddddd. When repeated infinitely, the th character is d.
Solution
The cipher repeats one RLE-encoded pattern forever, and we need a single character of it, possibly at an index past , so expanding the pattern is impossible.
Three steps, each a pattern from this module:
- Parse the RLE string into
(character, count)runs, accumulating multi-digit counts. - Reduce the index: with
Lthe total length of one copy, onlyc mod Lmatters. - Locate the run: sweep the runs, subtracting each count from the reduced index. The run where the counter first drops below zero contains the answer.
The last step in symbols: after subtracting counts , the answer is the first run with
Parsing is , summing the lengths is , and the sweep is . The total is linear in the encoding, independent of how large c is.
In C++, every count and the running index c must be long long, because both can reach , far past the range of int.
Implementation
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
long long c;
cin>>s>>c;
vector<pair<char,long long>> runs; // (character, count)
int i=0;
while(i<(int)s.size()){
char ch=s[i];
i++;
long long num=0;
while(i<(int)s.size() && isdigit(s[i])){
num=num*10+(s[i]-'0');
i++;
}
runs.push_back({ch,num});
}
long long length=0;
for(auto& run:runs){
length+=run.second;
}
c%=length;
for(auto& run:runs){
if(c<run.second){
cout<<run.first<<"\n";
break;
}
c-=run.second;
}
}Takeaways
- Frequency tables turn string questions into array questions. One pass to build, then answer from 26 counters instead of rescanning the string.
- Parse encodings into structured runs, not strings. A list of
(character, count)pairs answers length, position, and lookup questions without ever expanding the compressed form. - Modulo tames infinite repetition. For a pattern of length
Lrepeating forever, positioncis positionc mod Lof one copy. - Respect the bounds. Counts and indices up to mean 64-bit arithmetic in C++. A silent
intoverflow is a wrong answer, not a crash.