CCCGuide
Strings and Graphs

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.

num←10⋅num+digit\text{num} \gets 10 \cdot \text{num} + \text{digit}

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 101210^{12}, 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 2×1092 \times 10^9, 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:

c mod Lc \bmod L

The character at position c of the infinite repetition is the character at position c mod Lc \bmod L of one copy.

Positions count from 0 here. For positions counted from 1, use (c−1) mod L(c - 1) \bmod L.

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 TT and NN, representing the number of strings and the length of each string.

The next TT lines each contain a sequence of NN lowercase letters of the alphabet.

The following table shows how the available 1515 marks are distributed:

MarksBounds on TTBounds on NNOther Restrictions
552≤T≤42 \le T \le 42≤N≤42 \le N \le 4Only the letters "a" and "b" will be used
552≤T≤102 \le T \le 102≤N≤302 \le N \le 30None
222≤T≤1002 \le T \le 1002≤N≤1002 \le N \le 100Only the letter "a" will be heavy; all other letters are light
332≤T≤10 0002 \le T \le 10\ 0002≤N≤100  2 \le N \le 100~~None

Output TT lines, where each line will be either T or F. If the ii-th input string does alternate between light and heavy letters, the ii-th line of output should be T; and otherwise, the ii-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:

heavy(c)  ⟺  freq[c]>1\text{heavy}(c) \iff \text{freq}[c] > 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:

∀i:  heavy(si)≠heavy(si+1)\forall i: \; \text{heavy}(s_i) \neq \text{heavy}(s_{i+1})

If every pair satisfies this, print T. Otherwise print F. Building the table is O(N)O(N) per string and the scan is O(N)O(N), so the total is O(TN)O(TN).

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 cc-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 cc-th character of the long cipher that is formed by repeating this pattern infinitely?

The first line of input will consist of a string SS, representing a pattern produced by the RLE algorithm. The length of SS will be at least 22 and at most 2⋅1052\cdot 10^5. Additionally, all numbers appearing in SS are between 11 and 101210^{12}.

The next line of input contains a single integer cc, representing the index of the character you wish to locate, starting from index 00.

The following table shows how the available 1515 marks are distributed:

MarksBounds on ccAdditional Constraints
660≤c≤20000\le c \le 2000All numbers appearing in SS are between 11 and 99 (inclusive) and the length of the repeated pattern is at most 20002000 characters.
330≤c≤1060\le c \le 10^6The length of the repeated pattern is at most 10610^6 characters.
330≤c≤10120\le c \le 10^{12}The length of the repeated pattern is at most 10610^6 characters.
330≤c≤10120\le c \le 10^{12}No additional constraints.

Output the cc-th character of the long cipher.

r2d2
8

Output 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 c=8c = 8th character is r.

a4b1c2d10
100

Output 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 c=100c = 100th 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 101210^{12}, so expanding the pattern is impossible.

Three steps, each a pattern from this module:

  1. Parse the RLE string into (character, count) runs, accumulating multi-digit counts.
  2. Reduce the index: with L the total length of one copy, only c mod L matters.
  3. 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 k1,…,kmk_1, \dots, k_m, the answer is the first run jj with

c−(k1+⋯+kj)<0c - (k_1 + \dots + k_j) \lt 0

Parsing is O(∣S∣)O(|S|), summing the lengths is O(∣S∣)O(|S|), and the sweep is O(∣S∣)O(|S|). 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 101210^{12}, 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 L repeating forever, position c is position c mod L of one copy.
  • Respect the bounds. Counts and indices up to 101210^{12} mean 64-bit arithmetic in C++. A silent int overflow is a wrong answer, not a crash.

On this page