CF 69C - Game
Each player owns a multiset of artifacts. Some artifacts are basic and can be purchased directly. Some are composite and can be crafted from several basic artifacts in fixed quantities. Whenever a player buys a basic artifact, it is added to their inventory.
Rating: 2000
Tags: implementation
Solve time: 2m 15s
Verified: yes
Solution
Problem Understanding
Each player owns a multiset of artifacts. Some artifacts are basic and can be purchased directly. Some are composite and can be crafted from several basic artifacts in fixed quantities.
Whenever a player buys a basic artifact, it is added to their inventory. Immediately after that purchase, it may become possible to craft exactly one composite artifact. Crafting consumes the required quantities of its ingredients and adds one copy of the composite artifact. The statement guarantees that after every purchase there is at most one craftable composite artifact, so we never need to choose between multiple recipes.
The task is to simulate all purchases and finally print every player's inventory. Only artifacts with positive quantity should appear, and names must be printed in lexicographical order.
The constraints are small enough that direct simulation is feasible. There are at most 100 players, at most 50 recipes, and at most 500 purchases. Even a solution that scans all recipes after every purchase performs only about 500 × 50 = 25,000 recipe checks, which is tiny.
The main difficulty is not performance, but correct bookkeeping. Crafting removes components, so inventories must behave like multisets rather than simple sets. Another subtle point is that composite artifacts themselves can remain in inventories and coexist with basic artifacts.
A common mistake is forgetting to subtract ingredients after crafting. Consider this input:
1 2 1 2
wood
stone
axe: wood 1, stone 1
1 wood
1 stone
The correct final inventory is:
1
axe 1
A buggy implementation might output both wood, stone, and axe, because it crafts the item but forgets to consume the ingredients.
Another easy mistake is checking recipes only once globally instead of after every purchase for the corresponding player. Example:
1 1 1 2
gem
crown: gem 2
1 gem
1 gem
The first purchase does nothing. The second purchase creates crown. The correct output is:
1
crown 1
If we do not re-check after every purchase, the recipe is never triggered.
There is also a subtle parsing issue. Recipe lines contain commas and colons in a free-form textual format:
sword: iron 2, wood 1
A fragile parser that splits only on spaces will leave punctuation attached to names like "2," or "sword:". The safest approach is to first split around the colon, then split the ingredient list around commas.
Approaches
The brute-force idea is straightforward simulation.
For every purchase, add the bought artifact to the player's inventory. Then scan every recipe and check whether the player owns enough of every required ingredient. If some recipe is craftable, subtract the required counts and add one copy of the composite artifact.
This is correct because the statement guarantees that after each purchase at most one recipe can become craftable. We never face conflicting choices.
The brute-force method already fits comfortably within the limits. Let q be the number of purchases, m the number of recipes, and n the maximum number of ingredients in a recipe. The worst-case work is roughly q × m × n, which is at most 500 × 50 × 50 = 1,250,000 ingredient checks.
Since the constraints are tiny, there is no need for advanced graph processing or dependency tracking. The real work lies in implementing the simulation carefully.
The key observation is that crafting only depends on the current inventory of a single player. Purchases never interact across players, so each inventory can be stored independently. Once we represent each player's inventory as a frequency map, recipe checking becomes a direct comparison of required counts against owned counts.
The optimal solution is essentially the same simulation, but implemented cleanly with hash maps for inventories and parsed recipe structures.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(q × m × n) | O(k × A) | Accepted |
| Optimal | O(q × m × n) | O(k × A) | Accepted |
Here A is the total number of distinct artifact names.
Algorithm Walkthrough
- Read all basic artifact names.
We do not actually need to distinguish basic and composite artifacts during simulation, but reading the names establishes the universe of artifact identifiers. 2. Parse every composite recipe.
Each recipe contains a resulting artifact and a list of ingredient-count pairs. Store them as:
result_name -> list of (ingredient, required_count)
Parsing carefully matters because commas and colons are part of the input format. 3. Create one inventory map for each player.
Each inventory stores:
artifact_name -> quantity
- Process purchases one by one.
For a purchase (player, artifact):
-
Increase that player's count of the bought artifact.
-
Scan all recipes.
-
For each recipe, check whether the player owns enough of every ingredient.
-
If a recipe is craftable:
-
Subtract all ingredient counts.
-
Add one copy of the composite artifact.
-
Stop scanning recipes.
We stop immediately because the statement guarantees that at most one recipe can become available after a purchase. 5. After all purchases, print every player's inventory.
Only artifacts with positive counts should be printed. The output must be sorted lexicographically by artifact name.
Why it works
The invariant is that after processing each purchase, every player's inventory exactly matches the game rules.
When a purchase occurs, we first add the bought artifact. Then we check all recipes. If some recipe is craftable, we consume exactly the required ingredients and add exactly one resulting artifact. Because the statement guarantees uniqueness of the newly available recipe, stopping after the first successful craft matches the intended game behavior.
Since inventories are updated immediately after every event, the state before the next purchase is always correct. By induction over all purchases, the final inventories are correct.
Python Solution
import sys
from collections import defaultdict
input = sys.stdin.readline
def solve():
k, n, m, q = map(int, input().split())
basic = [input().strip() for _ in range(n)]
recipes = []
for _ in range(m):
line = input().strip()
result, rest = line.split(": ")
ingredients = []
parts = rest.split(", ")
for part in parts:
name, cnt = part.rsplit(" ", 1)
ingredients.append((name, int(cnt)))
recipes.append((result, ingredients))
inventories = [defaultdict(int) for _ in range(k)]
for _ in range(q):
player, artifact = input().split()
player = int(player) - 1
inventories[player][artifact] += 1
for result, ingredients in recipes:
ok = True
for name, need in ingredients:
if inventories[player][name] < need:
ok = False
break
if ok:
for name, need in ingredients:
inventories[player][name] -= need
inventories[player][result] += 1
break
out = []
for inv in inventories:
items = []
for name in sorted(inv.keys()):
if inv[name] > 0:
items.append((name, inv[name]))
out.append(str(len(items)))
for name, cnt in items:
out.append(f"{name} {cnt}")
print("\n".join(out))
solve()
The solution follows the simulation directly.
Recipes are stored as pairs of result name and ingredient list. The parsing uses split(": ") first because the colon separates the crafted artifact from its requirements. Then the ingredient section is split by ", " to isolate each requirement. Finally, rsplit(" ", 1) separates the ingredient name from its numeric quantity. Using rsplit is safer because artifact names are arbitrary strings.
Each player's inventory is a defaultdict(int), which automatically treats missing artifacts as quantity zero.
After every purchase, we scan recipes in input order. For each recipe, we verify whether every ingredient count is available. If so, we subtract the required quantities and add the crafted artifact.
One subtle implementation detail is stopping immediately after crafting. The statement guarantees that no more than one recipe becomes available after a purchase. Continuing the scan could incorrectly allow multiple crafts from a single event.
When printing results, we filter out zero counts because ingredients consumed during crafting may still remain as keys in the dictionary.
Worked Examples
Example 1
Input:
2 3 2 5
desolator
refresher
perseverance
vanguard: desolator 1, refresher 1
maelstorm: perseverance 2
1 desolator
2 perseverance
1 refresher
2 desolator
2 perseverance
Trace
| Step | Player | Purchase | Inventory Before Craft | Crafted | Inventory After |
|---|---|---|---|---|---|
| 1 | 1 | desolator | desolator=1 | none | desolator=1 |
| 2 | 2 | perseverance | perseverance=1 | none | perseverance=1 |
| 3 | 1 | refresher | desolator=1, refresher=1 | vanguard | vanguard=1 |
| 4 | 2 | desolator | perseverance=1, desolator=1 | none | perseverance=1, desolator=1 |
| 5 | 2 | perseverance | perseverance=2, desolator=1 | maelstorm | maelstorm=1, desolator=1 |
Final output:
1
vanguard 1
2
desolator 1
maelstorm 1
This example demonstrates ingredient consumption. After crafting vanguard, both desolator and refresher disappear from player 1's inventory.
Example 2
Input:
1 2 1 4
wood
stone
axe: wood 2, stone 1
1 wood
1 stone
1 wood
1 wood
Trace
| Step | Purchase | Inventory Before Craft | Crafted | Inventory After |
|---|---|---|---|---|
| 1 | wood | wood=1 | none | wood=1 |
| 2 | stone | wood=1, stone=1 | none | wood=1, stone=1 |
| 3 | wood | wood=2, stone=1 | axe | axe=1 |
| 4 | wood | axe=1, wood=1 | none | axe=1, wood=1 |
Final output:
2
axe 1
wood 1
This trace shows that crafting happens immediately when requirements become satisfied, and only the exact required quantities are consumed.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(q × m × n) | Each purchase scans all recipes and all recipe ingredients |
| Space | O(k × A) | Each player stores counts of owned artifacts |
The maximum work is around 1.25 million ingredient checks, which is very small for Python within a 2 second limit. Memory usage is also tiny because the total number of distinct artifact names is at most 100.
Test Cases
# helper: run solution on input string, return output string
import sys
import io
from collections import defaultdict
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
input = sys.stdin.readline
out_capture = io.StringIO()
k, n, m, q = map(int, input().split())
basic = [input().strip() for _ in range(n)]
recipes = []
for _ in range(m):
line = input().strip()
result, rest = line.split(": ")
ingredients = []
for part in rest.split(", "):
name, cnt = part.rsplit(" ", 1)
ingredients.append((name, int(cnt)))
recipes.append((result, ingredients))
inventories = [defaultdict(int) for _ in range(k)]
for _ in range(q):
player, artifact = input().split()
player = int(player) - 1
inventories[player][artifact] += 1
for result, ingredients in recipes:
ok = True
for name, need in ingredients:
if inventories[player][name] < need:
ok = False
break
if ok:
for name, need in ingredients:
inventories[player][name] -= need
inventories[player][result] += 1
break
ans = []
for inv in inventories:
items = []
for name in sorted(inv.keys()):
if inv[name] > 0:
items.append((name, inv[name]))
ans.append(str(len(items)))
for name, cnt in items:
ans.append(f"{name} {cnt}")
return "\n".join(ans)
# provided sample
assert run(
"""2 3 2 5
desolator
refresher
perseverance
vanguard: desolator 1, refresher 1
maelstorm: perseverance 2
1 desolator
2 perseverance
1 refresher
2 desolator
2 perseverance
"""
) == (
"""1
vanguard 1
2
desolator 1
maelstorm 1"""
), "sample 1"
# minimum input
assert run(
"""1 1 0 1
a
1 a
"""
) == (
"""1
a 1"""
), "minimum case"
# repeated crafting
assert run(
"""1 1 1 4
gem
crown: gem 2
1 gem
1 gem
1 gem
1 gem
"""
) == (
"""1
crown 2"""
), "multiple crafts over time"
# leftover ingredients
assert run(
"""1 2 1 3
wood
stone
axe: wood 2, stone 1
1 wood
1 stone
1 wood
"""
) == (
"""1
axe 1"""
), "exact consumption"
# independent player inventories
assert run(
"""2 1 1 2
gem
crown: gem 2
1 gem
2 gem
"""
) == (
"""1
gem 1
1
gem 1"""
), "players separated"
| Test input | Expected output | What it validates |
|---|---|---|
| Minimum input | Single artifact owned | Base initialization |
| Repeated crafting | crown 2 |
Crafting can happen multiple times across purchases |
| Leftover ingredients | Only axe 1 |
Ingredients are consumed correctly |
| Independent inventories | Each player keeps own gem | No accidental shared state |
Edge Cases
Consider the case where crafting consumes all ingredients exactly:
1 2 1 2
wood
stone
axe: wood 1, stone 1
1 wood
1 stone
After the second purchase, the inventory becomes:
wood=1, stone=1
The recipe is craftable, so both counts are reduced to zero and axe=1 is added. During output, zero-count entries are skipped. The final result is:
1
axe 1
Now consider delayed crafting:
1 1 1 3
gem
crown: gem 2
1 gem
1 gem
1 gem
After the first purchase, crafting is impossible. After the second, the inventory reaches gem=2, so crown is crafted and both gems disappear. After the third purchase, the inventory becomes:
crown=1, gem=1
The algorithm checks recipes after every purchase, so the timing is handled correctly.
Finally, consider multiple players:
2 1 1 2
gem
crown: gem 2
1 gem
2 gem
Player 1 owns one gem and player 2 owns one gem. Neither can craft crown, because inventories are independent. The algorithm stores a separate dictionary for each player, so counts are never mixed across players.