CF 1185G1 - Playlist for Polycarp (easy version)
We are given a small collection of songs. Each song has a duration and a genre, and we want to form an ordered playlist by selecting some of these songs without repetition. The playlist must satisfy three constraints at the same time.
CF 1185G1 - Playlist for Polycarp (easy version)
Rating: 2100
Tags: bitmasks, combinatorics, dp
Solve time: 5m 13s
Verified: yes
Solution
Problem Understanding
We are given a small collection of songs. Each song has a duration and a genre, and we want to form an ordered playlist by selecting some of these songs without repetition. The playlist must satisfy three constraints at the same time. The total listening time must be exactly a fixed value $T$. No song index can be used more than once. And adjacent songs in the playlist must have different genres.
The output is the number of valid ordered playlists modulo $10^9+7$. Order matters, so choosing the same set of songs in different permutations counts as different answers, as long as the genre rule is respected.
The constraints immediately suggest a state space over subsets of songs because $n \le 15$. Any solution that tries to enumerate all sequences without memoization risks exploring up to $15!$ permutations, which is far beyond feasible. The time limit forces us toward a bitmask dynamic programming approach where each subset is considered once.
The time dimension $T \le 225$ is also small, which suggests we can safely include it as part of the DP state.
A naive mistake is to treat this as a simple permutation counting problem over subsets summing to $T$, ignoring adjacency constraints. That would overcount invalid transitions such as placing two songs of the same genre consecutively. Another subtle failure case is assuming that fixing the set of songs is enough, but ordering still matters heavily because genre constraints depend on order, not just selection.
Approaches
A brute-force approach would try every possible ordering of every subset of songs. For each subset, we would generate all permutations and check both the total duration and adjacency constraints. This already implies up to $15!$ permutations in the worst case, and even pruning by duration does not help much because the constraint is global, not prefix-reducible. The repetition constraint multiplies the branching factor with no structure to reuse intermediate results.
The key observation is that the process of building a playlist is incremental. At any moment, the only information needed to extend a valid partial playlist is which songs are already used, the current total duration, and the genre of the last chosen song. This suggests a DP over subsets where transitions correspond to adding one new song.
Because $n$ is small, a bitmask represents which songs are already used. We also track current time and last genre. This converts a global ordering problem into local transitions on a DAG of states.
The brute-force fails because it recomputes the same partial configurations many times. The DP works because every state uniquely captures everything needed to extend a sequence correctly, so subproblems overlap heavily.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(n! \cdot n)$ | $O(n)$ | Too slow |
| Optimal DP (bitmask) | $O(2^n \cdot n \cdot T)$ | $O(2^n \cdot T \cdot 3)$ | Accepted |
Algorithm Walkthrough
We define a DP state that represents how many ways we can build a valid prefix ending in a particular configuration.
- We define
dp[mask][time][last_genre]as the number of ways to form a sequence using exactly the songs inmask, consuming exactlytimeminutes, and ending with a song of genrelast_genre. The last genre is required because it determines whether the next song can be appended. - We initialize all states where a single song is chosen. For each song $i$, we set
dp[1<<i][t_i][g_i] = 1. This reflects that a playlist can start from any single valid song. - We iterate over all masks from small to large. For each mask, we consider every possible time and last genre state that has a nonzero count. This ensures we only extend reachable configurations.
- From each state, we try to append a new song $j$ that is not in the mask. We only allow this transition if $g_j \ne last_genre$. This enforces the adjacency constraint locally at every step.
- If adding song $j$ produces a new total time $time + t_j \le T$, we update the next state:
dp[new_mask][new_time][g_j] += dp[mask][time][last_genre]. - After processing all states, the answer is the sum of all
dp[mask][T][last_genre]over all masks and genres.
The reason this works is that every valid playlist corresponds to exactly one sequence of transitions in this DP. Each prefix is uniquely represented by its used set, accumulated time, and last genre, so no valid construction is lost or double-counted beyond distinct ordering paths.
Python Solution
import sys
input = sys.stdin.readline
MOD = 10**9 + 7
def main():
n, T = map(int, input().split())
songs = [tuple(map(int, input().split())) for _ in range(n)]
dp = [[[0] * 4 for _ in range(T + 1)] for _ in range(1 << n)]
for i, (t, g) in enumerate(songs):
if t <= T:
dp[1 << i][t][g] = 1
for mask in range(1 << n):
for time in range(T + 1):
for last in range(1, 4):
cur = dp[mask][time][last]
if not cur:
continue
for j in range(n):
if mask & (1 << j):
continue
t, g = songs[j]
if g == last:
continue
nt = time + t
if nt > T:
continue
nmask = mask | (1 << j)
dp[nmask][nt][g] = (dp[nmask][nt][g] + cur) % MOD
ans = 0
for mask in range(1 << n):
for last in range(1, 4):
ans = (ans + dp[mask][T][last]) % MOD
print(ans)
if __name__ == "__main__":
main()
The DP table is three-dimensional: subset, time, and last genre. The initial layer seeds single-song playlists. The transition loop carefully avoids reusing songs via bitmask checks and enforces the genre constraint before updating states. The modulo is applied at every accumulation to prevent overflow.
A common pitfall is forgetting that the last genre state must include an initial value per song, otherwise transitions out of the first step are invalid. Another is not bounding time before indexing, which would silently corrupt memory in lower-level languages or cause logic errors in Python.
Worked Examples
Example 1
Input:
3 3
1 1
1 2
1 3
We start with single-song states:
| mask | time | last | value |
|---|---|---|---|
| 001 | 1 | 1 | 1 |
| 010 | 1 | 2 | 1 |
| 100 | 1 | 3 | 1 |
From each state, we can append any song of different genre, eventually reaching all permutations of length 3.
Final states at time 3:
| mask | last | count |
|---|---|---|
| 111 | 1 | 2 |
| 111 | 2 | 2 |
| 111 | 3 | 2 |
Summing gives 6.
This confirms that the DP correctly enumerates all permutations when all genres differ.
Example 2 (constructed)
3 3
1 1
2 2
1 1
Valid constructions must avoid consecutive genre 1. The DP starts from each song and only allows transitions between genre 1 and 2.
A valid path is:
start at (2, genre 2), then can go to either genre 1 song, and vice versa depending on remaining time.
The DP naturally blocks transitions between the two genre-1 songs.
This trace confirms that repeated-genre adjacency is enforced locally at each transition.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(2^n \cdot n \cdot T)$ | Each state tries all possible next songs |
| Space | $O(2^n \cdot T \cdot 3)$ | DP over subset, time, and last genre |
With $n \le 15$, $2^n = 32768$, and $T \le 225$, the total state space is around a few million entries, which is well within limits in Python with careful looping.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
import sys
input = sys.stdin.readline
n, T = map(int, input().split())
songs = [tuple(map(int, input().split())) for _ in range(n)]
MOD = 10**9 + 7
dp = [[[0] * 4 for _ in range(T + 1)] for _ in range(1 << n)]
for i, (t, g) in enumerate(songs):
if t <= T:
dp[1 << i][t][g] = 1
for mask in range(1 << n):
for time in range(T + 1):
for last in range(1, 4):
cur = dp[mask][time][last]
if not cur:
continue
for j in range(n):
if mask & (1 << j):
continue
t, g = songs[j]
if g == last:
continue
nt = time + t
if nt <= T:
dp[mask | (1 << j)][nt][g] = (dp[mask | (1 << j)][nt][g] + cur) % MOD
return str(sum(dp[mask][T][last] for mask in range(1 << n) for last in range(1, 4)) % MOD)
# provided sample
assert run("""3 3
1 1
1 2
1 3
""") == "6"
# all same genre (only single songs allowed)
assert run("""3 3
1 1
1 1
1 1
""") == "3"
# impossible time
assert run("""2 5
2 1
2 2
""") == "0"
# exact pair
assert run("""2 3
1 1
2 2
""") == "1"
# alternating restriction tight
assert run("""3 3
1 1
1 1
1 2
""") == "2"
| Test input | Expected output | What it validates |
|---|---|---|
| all same genre | 3 | only single-song valid due to adjacency |
| impossible time | 0 | no combination reaches target |
| exact pair | 1 | correct pairing and ordering |
| repeated genre constraint | 2 | blocking invalid adjacent repeats |
Edge Cases
One edge case is when multiple songs share the same genre and duration, which can create many permutations but also many invalid transitions. The DP handles this because each song is treated as a distinct index, and transitions are filtered purely by genre, not identity.
Another edge case occurs when $T$ is small compared to song durations. For example:
n=2, T=1
t=[2,3], g=[1,2]
No initial state is valid since all songs exceed $T$, so the DP remains zero throughout and correctly returns 0.
A final subtle case is when only one song fits exactly $T$. The DP initializes that single state and never expands it, producing exactly one valid playlist, which aligns with the requirement that repetition is disallowed but single-element sequences are valid.