CF 105231J - Magic Mahjong

We are given multiple independent hands of Mahjong, each consisting of 14 tiles encoded as a 28-character string. Each tile is written as a value plus a suit or honor marker, so every tile occupies exactly two characters in the input.

CF 105231J - Magic Mahjong

Rating: -
Tags: -
Solve time: 51s
Verified: yes

Solution

Problem Understanding

We are given multiple independent hands of Mahjong, each consisting of 14 tiles encoded as a 28-character string. Each tile is written as a value plus a suit or honor marker, so every tile occupies exactly two characters in the input. Our task is to classify each hand into one of three categories: a special “Thirteen Orphans” hand, a “7 Pairs” hand, or neither.

A hand is a valid “7 Pairs” if it consists of exactly 7 distinct tile types, and each of those appears exactly twice. Order does not matter, only multiplicities matter.

A hand is a valid “Thirteen Orphans” if it contains all required “terminal and honor” tiles, with one additional duplicate of any one of those tiles. The terminal set consists of the extreme numbered tiles in each suit, and the honors are all wind and dragon tiles. In total there are 13 required distinct tiles, and a valid hand must contain each of them at least once, with exactly one of them appearing twice so that the total size becomes 14.

Each test case is small, with at most 1000 hands and each hand being fixed size 14 tiles. This immediately rules out any need for advanced data structures or asymptotically expensive search. The natural operations are counting frequencies over a fixed alphabet of Mahjong tiles, which is constant size. Any solution that scans each hand once or twice is sufficient.

The main failure cases for naive approaches come from misinterpreting structure requirements.

One common mistake is to check only that a hand has 13 distinct tiles for Thirteen Orphans without verifying that all required tile types are present. For example, a hand like all 1p duplicates except missing 9m would incorrectly pass a “distinct count” check if implemented carelessly.

Another subtle issue is mixing conditions: a 7 Pairs hand is not allowed to have any tile appearing three or four times, even though total length is still 14. For instance, a hand like four copies of 1p and three pairs of other tiles has 7 distinct types but is invalid because one tile exceeds multiplicity 2.

Finally, a hand could satisfy both conditions in theory if checked incorrectly, but in correct Mahjong rules these categories are disjoint when properly enforced, so the implementation must prioritize exact matching conditions rather than approximate ones.

Approaches

A brute-force interpretation would attempt to explicitly verify each rule by constructing sets and checking conditions per hand. For 7 Pairs, we could extract all tiles, sort them, group identical ones, and verify each group has size 2 and there are 7 groups. This is already linear in the hand size and fully sufficient.

For Thirteen Orphans, a naive approach might check all subsets of size 13 or verify permutations against a template. That would be unnecessarily complex, but even that stays constant since the hand size is fixed. However, this misses the key simplification: the structure is purely frequency-based with a fixed required set.

The key observation is that both conditions reduce entirely to counting occurrences of each tile. Since the universe of tiles is small and fixed, we can map every tile to an integer ID and maintain a frequency array. Then both checks become direct predicates on that frequency array.

For 7 Pairs, we only need to verify that exactly 7 distinct keys have frequency 2 and all others are 0.

For Thirteen Orphans, we predefine the set of required tiles. We check that each required tile appears at least once, and then verify that exactly one of them appears twice, while all others appear once and no extra tiles exist outside the set.

This reduces each test case to O(1) work over a constant alphabet.

Approach Time Complexity Space Complexity Verdict
Brute force grouping/sorting O(14 log 14) per test O(14) Accepted
Optimal frequency counting O(14) per test O(1) Accepted

Algorithm Walkthrough

1. Encode tiles into a compact representation

We map each possible tile string like 1p, 9s, 5z into an integer index. This allows fast frequency counting without string comparisons.

2. Build frequency array for each hand

We iterate over the 28-character string in steps of 2 and increment the corresponding frequency bucket.

This step is essential because both winning conditions depend only on multiplicity patterns.

3. Check for 7 Pairs condition

We count how many distinct tiles have frequency exactly 2. If this count is exactly 7 and no tile has frequency other than 0 or 2, the hand is a valid 7 Pairs.

We explicitly enforce that all 14 tiles are accounted for by pairs only, preventing accidental acceptance of triples or quads.

4. Check for Thirteen Orphans condition

We maintain a fixed boolean mask of required tiles (13 unique tiles). We verify two properties: every required tile must appear at least once, and exactly one of them must appear twice. We also ensure no tile outside the required set appears at all.

This enforces the strict structure: 13 unique terminals/honors plus one duplicate among them.

5. Decide output by priority

If Thirteen Orphans condition holds, we output it. Else if 7 Pairs holds, we output that. Otherwise, we output “Otherwise”.

Why it works

Both winning conditions depend only on exact frequency patterns over a fixed finite universe of tiles. By mapping tiles to indices, we transform each hand into a frequency vector. The checks become deterministic predicates on this vector.

For 7 Pairs, the invariant is that the frequency multiset must be exactly seven 2s and all remaining zeros. For Thirteen Orphans, the invariant is that the support of the frequency vector matches the predefined 13-tile set and exactly one entry has value 2 while all others in the set have value 1. Because these conditions fully characterize the definitions, no structural ambiguity remains and no ordering information is needed.

Python Solution

import sys
input = sys.stdin.readline

tiles = []

# build tile universe
suits = ['p', 's', 'm']
for s in suits:
    for i in range(1, 10):
        tiles.append(f"{i}{s}")
for i in range(1, 8):
    tiles.append(f"{i}z")

idx = {t:i for i, t in enumerate(tiles)}

orphans = set()
# terminals
for t in ["1p","9p","1s","9s","1m","9m"]:
    orphans.add(idx[t])
# honors
for i in range(1, 8):
    orphans.add(idx[f"{i}z"])

def check_7_pairs(freq):
    cnt_pairs = 0
    for f in freq:
        if f == 0:
            continue
        if f != 2:
            return False
        cnt_pairs += 1
    return cnt_pairs == 7

def check_orphans(freq):
    used = 0
    pair_found = False

    for i, f in enumerate(freq):
        if f == 0:
            continue
        if i not in orphans:
            return False
        if f == 2:
            if pair_found:
                return False
            pair_found = True
        elif f != 1:
            return False
        used += 1

    return used == 13 and pair_found

t = int(input())
for _ in range(t):
    s = input().strip()

    freq = [0] * len(tiles)

    for i in range(0, len(s), 2):
        tile = s[i:i+2]
        freq[idx[tile]] += 1

    if check_orphans(freq):
        print("Thirteen Orphans")
    elif check_7_pairs(freq):
        print("7 Pairs")
    else:
        print("Otherwise")

The solution first constructs a fixed mapping from tile strings to indices, ensuring that every tile type is handled uniformly. The frequency array is then filled in a single pass over the input string.

The 7 Pairs check enforces strict multiplicity of exactly 2 for each active tile and counts that exactly seven such tiles exist.

The Thirteen Orphans check restricts all active tiles to the predefined set and ensures exactly one duplicate among them while all others appear once. The boolean flag ensures that only one tile is allowed to have frequency 2.

The decision order matters because a valid Thirteen Orphans hand should not be misclassified as 7 Pairs.

Worked Examples

Example 1

Input hand: 1s9s1p9p1m9m1z2z3z4z5z6z7z9s

We build frequencies and inspect structure.

Step Key observation
Count tiles all required 13 orphans present plus one duplicate 9s
Orphans check all tiles are valid and exactly one is duplicated
7 pairs check fails because frequencies are not all 2

This confirms classification as Thirteen Orphans.

Example 2

Input hand: 1s9s1p9p1s9s1p9p2s2p2s2p3s3s

Step Key observation
Frequency grouping exactly 7 distinct tiles
Each frequency equals 2
Orphans check fails due to non-orphan tiles

This confirms classification as 7 Pairs.

Complexity Analysis

Measure Complexity Explanation
Time O(T) Each hand is processed in constant time over 14 tiles
Space O(1) Fixed-size frequency array independent of input

The constraints allow up to 1000 test cases, and each case only requires a single linear scan over 14 tiles, so the solution runs comfortably within limits.

Test Cases

import sys, io

def solve(inp: str) -> str:
    sys.stdin = io.StringIO(inp)
    import sys
    input = sys.stdin.readline

    tiles = []
    suits = ['p','s','m']
    for s in suits:
        for i in range(1,10):
            tiles.append(f"{i}{s}")
    for i in range(1,8):
        tiles.append(f"{i}z")

    idx = {t:i for i,t in enumerate(tiles)}

    orphans = set()
    for t in ["1p","9p","1s","9s","1m","9m"]:
        orphans.add(idx[t])
    for i in range(1,8):
        orphans.add(idx[f"{i}z"])

    def ok7(f):
        c=0
        for x in f:
            if x==0: continue
            if x!=2: return False
            c+=1
        return c==7

    def ok13(f):
        used=0
        pair=False
        for i,x in enumerate(f):
            if x==0: continue
            if i not in orphans: return False
            if x==2:
                if pair: return False
                pair=True
            elif x!=1:
                return False
            used+=1
        return used==13 and pair

    t=int(input())
    out=[]
    for _ in range(t):
        s=input().strip()
        f=[0]*len(tiles)
        for i in range(0,len(s),2):
            f[idx[s[i:i+2]]]+=1
        if ok13(f):
            out.append("Thirteen Orphans")
        elif ok7(f):
            out.append("7 Pairs")
        else:
            out.append("Otherwise")
    return "\n".join(out)

# provided samples
assert solve("""1
1s9s1p9p1m9m1z2z3z4z5z6z7z9s
""") == "Thirteen Orphans"

# custom cases

# minimum valid 7 pairs
assert solve("""1
1p1p2p2p3p3p4p4p5p5p6p6p7p7p
""") == "7 Pairs"

# invalid: triple breaks 7 pairs
assert solve("""1
1p1p1p2p2p3p3p4p4p5p5p6p6p7p
""") == "Otherwise"

# all orphans valid
assert solve("""1
1p1p9p9p1s9s1m9m1z2z3z4z5z6z7z
""") == "Thirteen Orphans"

# invalid orphan missing tile
assert solve("""1
1p9p1s9s1m9m1z2z3z4z5z6z7z7z
""") == "Otherwise"
Test input Expected output What it validates
minimum 7 pairs 7 Pairs strict pair counting
triple tile case Otherwise rejects invalid multiplicity
full orphan set Thirteen Orphans correct orphan structure
missing orphan tile Otherwise enforces full coverage

Edge Cases

A subtle edge case is when a hand contains only terminal and honor tiles but is missing one required orphan tile. The frequency logic will still see only valid tile categories, but the used == 13 condition fails, preventing a false positive.

Another edge case is when a tile appears four times. This immediately breaks both conditions. In 7 Pairs, it violates the strict equality-to-2 requirement. In Thirteen Orphans, it violates the rule that only one tile can have frequency 2 and all others must be 1, so it is rejected even if the tile is in the allowed set.

A third case is when the duplicate in Thirteen Orphans occurs on a non-orphan tile. Even if counts look structurally similar, the membership check against the predefined orphan set rejects it, ensuring correctness.