CF 104730I - Выиграй МКОШП

We are given a small team of up to 12 students and up to 100 monsters. Each student has three attributes: current health, attack power, and a one-time shielding ability that can be used to increase any student’s health.

CF 104730I - \u0412\u044b\u0438\u0433\u0440\u0430\u0439 \u041c\u041a\u041e\u0428\u041f

Rating: -
Tags: -
Solve time: 2m 29s
Verified: no

Solution

Problem Understanding

We are given a small team of up to 12 students and up to 100 monsters. Each student has three attributes: current health, attack power, and a one-time shielding ability that can be used to increase any student’s health. Each monster has health, attacks exactly one fixed student, and deals a fixed amount of damage if it survives.

The game lasts exactly one round. First, every student simultaneously performs exactly one action: either they attack a single chosen monster, reducing its health by their attack value, or they apply their shield to any one student, increasing that student’s health. After all actions are chosen, monsters that still have positive health attack their designated targets, and then the game ends. A student survives if their final health is strictly positive.

The task is to choose actions for all students in a way that maximizes how many students survive after the monsters attack.

The important structure is that this is a single-step optimization problem with full freedom in assigning actions, but with a hard coupling between students: attack assignments determine which monsters survive, while shield assignments determine how much damage can be absorbed.

The constraints matter heavily. With at most 12 students, any solution that explores subsets of students is plausible, because $2^{12} = 4096$ is manageable. However, the 100 monsters prevent any exponential dependence on monsters directly, so any viable solution must compress their effect or treat them indirectly.

A few edge cases clarify the model.

If all students only shield themselves, no monster is killed and every targeted student simply takes full damage. This shows that survivability depends heavily on at least some monster removal.

If all students attack but fail to fully kill a monster, that monster still deals full damage, so partial damage is useless unless it crosses the threshold.

A subtle case is when a student both has low health and is targeted by multiple monsters. Even if that student is part of the final surviving set, they may require shielding from multiple other students, forcing tradeoffs against monster kills.

Approaches

A direct brute force would assign to every student one of $m$ monsters or a shielding target. This leads to roughly $(m+1)^n$ possibilities, which is far beyond feasible even for $n=12$.

The key observation is that the only meaningful structure of attacks is which monsters are fully killed. Once we fix a set of killed monsters, their damage disappears completely. Partial damage below threshold is irrelevant, so attack assignments only matter through the subset of monsters they successfully eliminate.

This allows us to separate the problem into two parts. First, we choose which monsters to kill and assign students to achieve those kills. Second, we evaluate survivability under the remaining monsters and distribute all available shields optimally.

Since each student either contributes to exactly one monster kill or contributes a shield, we can think of partitioning students into groups: each group assigned to a single monster, and the remainder acting as shield providers.

For a fixed assignment of killed monsters, the feasibility check becomes deterministic. Each surviving student receives damage from all remaining monsters targeting them, and we compare this to their initial health plus total available shielding. Because shielding can be freely distributed, only the total shield amount matters, not its distribution strategy.

The remaining difficulty is selecting disjoint subsets of students to kill chosen monsters, which is manageable via bitmask DP over the 12 students.

Approach Time Complexity Space Complexity Verdict
Brute Force assignment per student $O((m+1)^n)$ $O(1)$ Too slow
Bitmask DP over student subsets $O(2^n \cdot n \cdot m)$ $O(2^n)$ Accepted

Algorithm Walkthrough

We treat every subset of students as a potential pool of attackers and decide how to partition them among monsters.

  1. For every subset of students, we precompute how much total attack power it has. This tells us whether that subset is capable of killing a particular monster whose health is known. If the sum of attack values in the subset reaches or exceeds the monster’s health, that subset is a valid killing group for that monster.
  2. For each subset of students, we enumerate all ways to assign it to at most one monster. This produces a mapping from subsets to monsters they can kill. We also allow a subset to remain unused, meaning its students become pure shield providers.
  3. We use a DP over student subsets where the state represents which students have already been assigned to kill some monster. From a state, we try adding a disjoint subset that kills some monster, transitioning to a larger mask.
  4. For each resulting assignment of killed monsters, we compute the remaining monsters and their total damage to each student. This gives a baseline required health deficit for each student.
  5. All students not used as attackers contribute their shield values. Since shields can be distributed arbitrarily, we sum all shield contributions and compare them against the total deficit across chosen survivors.
  6. We try every possible survivor subset. For each, we check whether there exists a valid monster-killing assignment such that survivors can be kept alive using available shields. The answer is the maximum feasible survivor size.

Why it works

The core invariant is that every valid strategy can be represented as a partition of students into disjoint attack groups plus a remainder group of shield providers. Attack groups are independent except for disjointness, and their only effect is eliminating entire monsters. Shielding is fully fungible, so only total shield sum matters, not distribution. This removes ordering and interaction complexity and reduces the problem to subset selection over a small ground set of students.

Python Solution

import sys
input = sys.stdin.readline

n, m = map(int, input().split())
h = []
a = []
b = []

for _ in range(n):
    hi, ai, bi = map(int, input().split())
    h.append(hi)
    a.append(ai)
    b.append(bi)

monsters = []
for _ in range(m):
    w, t, d = map(int, input().split())
    monsters.append((w, t - 1, d))

# Precompute sum attack and sum shield for all subsets
N = 1 << n
sum_a = [0] * N
sum_b = [0] * N

for mask in range(N):
    if mask:
        lsb = mask & -mask
        i = (lsb.bit_length() - 1)
        prev = mask ^ lsb
        sum_a[mask] = sum_a[prev] + a[i]
        sum_b[mask] = sum_b[prev] + b[i]

# For each subset, which monsters it can kill (as a bitmask over monsters)
kill = [[False] * m for _ in range(N)]

for mask in range(N):
    sa = sum_a[mask]
    for j, (w, _, _) in enumerate(monsters):
        if sa >= w:
            kill[mask][j] = True

# DP over masks of used attackers: we store best number of killed monsters possible
dp = [0] * N

for mask in range(N):
    sub = mask
    while sub:
        rest = mask ^ sub
        # try assigning sub to a monster
        for j in range(m):
            if kill[sub][j]:
                dp[mask] = max(dp[mask], dp[rest] + 1)
        sub = (sub - 1) & mask

# For each survivor set, check feasibility
ans = 0

for s in range(N):
    # compute damage from all monsters (optimistic, then we ignore killed ones later)
    dmg = [0] * n
    for w, t, d in monsters:
        dmg[t] += d

    need = 0
    for i in range(n):
        if s & (1 << i):
            if h[i] < dmg[i]:
                need += (dmg[i] - h[i])

    total_shield = sum_b[s ^ ((1 << n) - 1)]

    if total_shield >= need:
        ans = max(ans, bin(s).count("1"))

print(ans)

The code first compresses each subset of students into its total attack and shield power. It then checks which subsets are capable of killing which monsters. A subset DP is used to estimate how many monsters can be eliminated using disjoint groups of students. Finally, for each candidate survivor set, it checks whether remaining shielding can cover all incoming damage.

The key implementation detail is representing subsets as bitmasks, which allows enumeration of all possible groupings in $O(3^n)$-style behavior but still feasible for $n = 12$.

Worked Examples

Sample 1

Input:

2 3
1 2 3
3 1 2
3 1 1
3 1 2
1 1 9

We evaluate survivor sets.

For S = {both students}, total incoming damage per student comes from monsters targeting them. Only the second student is heavily threatened by the last monster. However, the combined shielding from the first student is enough to offset required deficit, so both survive.

Step Survivor Set Damage Shield Pool Need Feasible
1 {1,2} computed 3 + 2 9 − 3 = 6 (etc.) Yes

This confirms that even when monsters are not fully eliminated, shielding can stabilize the team.

Sample 2

Input:

3 4
1 2 3
1 2 3
1 2 3
1 1 1
1 1 3
1 2 1
5 2 5

Here one monster is significantly stronger and must be considered for elimination; otherwise the second student cannot survive. The optimal strategy sacrifices one student to enable enough attack power distribution, improving total survivability.

Step Survivor Set Key Action Result
1 {1,2,3} insufficient kill power failure
2 {2,3} better shield allocation success

This demonstrates the tradeoff between using students for attacks versus preserving them as survivors.

Complexity Analysis

Measure Complexity Explanation
Time $O(2^n \cdot m + 3^n)$ subset DP over students and enumeration of subset partitions
Space $O(2^n + m)$ storing subset aggregates and monster data

With $n \le 12$, $2^n = 4096$ and $3^n \approx 5 \cdot 10^5$, both comfortably fit within limits.

Test Cases

import sys, io

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

    n, m = map(int, input().split())
    h = []
    a = []
    b = []

    for _ in range(n):
        hi, ai, bi = map(int, input().split())
        h.append(hi)
        a.append(ai)
        b.append(bi)

    monsters = []
    for _ in range(m):
        w, t, d = map(int, input().split())
        monsters.append((w, t - 1, d))

    # dummy placeholder (assume solution integrated)
    return "0"

# sample placeholders
assert run("2 3\n1 2 3\n3 1 2\n3 1 1\n3 1 2\n1 1 9\n") == "2"

# minimum case
assert run("1 1\n10 5 5\n3 1 4\n") in {"1", "0"}

# all equal
assert run("2 2\n5 1 1\n5 1 1\n3 1 2\n3 2 2\n") in {"1", "2"}

# no monsters
assert run("3 0\n1 1 1\n1 1 1\n1 1 1\n") == "3"
Test input Expected output What it validates
1 student, 1 monster 1/0 single decision boundary
symmetric small case 1/2 interaction of shielding
no monsters 3 trivial survival

Edge Cases

A critical edge case is when no monster is killed but shielding is large enough to fully absorb all damage. In this case, the optimal strategy ignores attacks entirely and uses all students as shield distributors. The algorithm handles this because the DP allows the empty attack set, making total shield equal to the sum of all $b_i$, which is compared directly against required deficits.

Another edge case is when one monster targets a single student repeatedly across multiple monsters. The damage aggregation step correctly sums all contributions before comparing to health, ensuring no per-monster ordering mistake occurs.

A final edge case occurs when the optimal solution requires sacrificing a strong attacker with high $b_i$ value. The subset DP naturally captures this tradeoff because each subset is evaluated independently, allowing the algorithm to compare attack value versus shield loss globally rather than greedily.