Sunday, August 6th

 How to Make a Mixtape

Given a number of songs, and a target number of tracks, create a playlist with every song played at least once.  A song can also be replayed only after a given number k songs have been played

Image courtesy of Antony Mayfield via Wikipedia, shared under the Creative Commons Attribution 2.0 Generic license.  No changes made.

While this is a largely combinatorial problem, it's clear we can't simply enumerate all possible track configurations.  This is hinted at by both the large contraints of the problem (100), as well as the final answer to the problem returns modulo 10^9+7

So, instead we turn to dynamic programming.  We consider 2 state variables, a variable i to represent the number of tracks, and j to represent the number of distinct tracks.  The value dp[i][j] represents the number of possible playlists of size i with j unique tracks

The goal is to find dp[goal][n], where n is the total number of unique tracks and goal is the target number of total tracks.  Both these parameters are defined by the problem.  Let us approach the problem recursively

Global variables

vector<vector<long> > dp;
const int MOD = 1e9 + 7;
int kk;
int nn;

and main function calling the recursive function.  The dynamic programming table dp is initialized with dimensions of goal+1 and n+1

    int numMusicPlaylists(int n, int goal, int k) {
// initializing global variables
dp = vector<vector<long> >(goal + 1, vector<long>(n + 1, -1))
kk = k;
nn = n;
//return the recursive function
return recurse(goal, n);
}

To approach the problem recursively, we define a function that will return the number of playlists for a given goal number of tracks with n unique songs.  Because of the large problem space, the function will return a long.

long recurse(int i, int j) {
        ....
    }
           
The recursive function begins with base cases.  If both and j are 0, then there is only 1 way to meet this spec - an empty table.  Otherwise, if either and j are 0 while the other is not, then this is contradictory and cannot happen, so return 0.

// Base cases
if (i == 0 && j == 0) return 1;
if (i == 0 || j == 0) return 0;
   
If an entry in the dp table already exists for a given combination, then return it

if (dp[i][j] != -1) return dp[i][j];

Now for the meat, and recurrence relation.  There are 2 possible situations for when a song is added:
  • It's a new song
  • It's a repeated song
If it's a new song, then the length of the playlist increases by 1, as does the number of unique songs.  So the new number of possibilities will transition from dp[i-1][j-1]

Since the number of songs in the derivative playlist is j-1, and there are n total songs, then the number of unplayed available songs is n-(j-1), or n-j+1.  So for calculating all possible combinations, we multiply dp[i-1][j-1] by n-j+1 (then modulo the answer)

    dp[i][j] = (recurse(i - 1, j - 1) * (nn - j + 1)) % MOD;

There is one other case to consider: when the length of the playlist increases, but the number of unique songs stays the same because we are reusing a previously played track.  This means we are transitioning from dp[i-1][j], since j doesn't increase

We need to calculate how many tracks are available to choose from.  There are j previously played tracks, however we must consider the problem constraint that a song can be replayed only after k other songs have been played.  

If the number of played tracks is j = k-5, then since j<k we can't play any of the previous tracks.  This leads us to, in order to play a previously played track, j must be greater than k.  In such an instance, then we can play up to j - k tracks
 
    if (j > kk) {
dp[i][j] += (recurse(i - 1, j) * (j - kk)) % MOD;
dp[i][j] %= MOD;

The two situations are added together and modded appropriately

To complete the recursive function we return the newly calculated value

    return dp[i][j];

A tricky problem, but not too bad once you figure out the state variables

A nod to the Editorial for a clear and concise explanation

Comments

Popular posts from this blog

Wednesday, September 20th

Monday, September 11th

Tuesday, September 19th