Return to the Index of Meeting Notes and Topics
Readings and Problems
Procedural Information
We discussed the purpose and format of the course, as well as policies and procedures. This discussion was derived from the course syllabus. Please refer to that document for more information.
The Contest Environment
We discussed the programming contest environment, including the types of problems, resources, and team environment. Although this discussion was based on our years of coaching, it is also well-represented by the reading. As a result, we're not providing extensive notes -- we'll just refer you there. But, please do let us know if you have any questions. We're here to help!
Review of Selected Problems from the ECNA Regions
Carnegie Mellon competes in the Eastern Central North American (ECNA) region of the ACM's International Collegiate Programming Competition (ICPC).Today we discussed problems A, B, C, F, and G from the 2004 regionals. These were the problems that were solved by either or both of our teams during that competition. The remaining problems were assigned for the in-class team programming and homework.
- Problem A
This problem asks us to determine the number of strings represented by an ambiguous encoding for strings.
The code represents a string of letters by replacing each letter within a string with its position in the alphabet (e.g, A=1, B=2, C=3, &c). For example, the string "ABC" is encoded as "123".
As we mentioned, this encoding is ambiguous. Because the encoding concatenates the numbers together without delimiting, multiple strings share the same encoding. For example, "13" might represent the string "M" (13), or the string "AC" (1,3).
To solve this problem we observe that there are 26 letters in the alphabet, numbered 1-26. As a result, we can infer several other important things about the encoding:
- The digit 0 must be the second digit of the number 10, representing J, or the number 20, representing T.
- The digits 7-9 can only be part of a single digit (G-I), or numbers 17-19 (Q-s).
- The digits 1-6 can only be part of a single digit number, 1-9 (A-F), or the numbers 11-16 (K-P) or 21-26 (V-Z).
To solve this problem, we move from left to right, accumulating possible interpretations. How many new interpretations we add depends on whether or not the new digit and its predecessor can form a new digit, e.g. 2+6 is a new digit, but 2+9 is not.
// Pseudo-code only -- not compilable int alphaCode (string code, int substring) { // Sanity check if (code[0] == 0) throw NotALetterException("A is 1, not 0"); // Freebie: Only one digit and it is valid. Easy. One read. if (code.length == 1) return 1; /* * Base case -- let's prime the pump and figure out * how many interpretations each of substring (code, 0) and * substring (code, 1) have. */ // We know that the substring(code,0) has only 1 interpretation // because it is only one letter. int predPredDecodings = 1; // Now, we find out about substring(code, 1). It depends if // both digits taken together form a letter. int predDecodings; if ( (Letter (A[0], A[1])) && Letter[0] != 0) predDecodings = 2; else predDecodings = 1; /* * Iterative case. * * Now as we consider each subsequent letter, the number of * possible decodings will be predDecodings, unless the new * digit allows the predecessor to be interpreted not only * as before, but also as a new letter, in which case there * are (predDecodings + predPredDecodings) possibilities * 1st + 2nd form a letter, */ for (int i = 2; i < n; i++) { int decodings; if (coe[i] == 0) decoding=predPredDecodings; else if (Letter(code[i-1], code[i]) decodings = predPredDecodings + predDecodings; else decodings = predDecodings; // Set up for next time predPredDecodings = predDecodings; predDecodings = decodings; } return predDecodings; } boolean Letter (int digit0, int digit1) { /* * Make sure letters are in the range 1, 2, 3, ..., 26 */ if (digit0 == 0) return false; return ( (digit0==1) || ((digit0==2) && (digit1 < 6)) ); }
- Problem B
This problem asks us to rearrange consecutive natural numbers, n, ..., m into an antiprime sequence such that all subsequences of length 2, ..., d, sum to a composite number. For example, if the natural numbers are 1, ..., 10, and d=3, then a possible rearrangement is 1, 3, 5, 4, 6, 2, 10, 8, 7, 9. If multiple sequences exist, return the lexicographically first sequence. If no such sequence exists, the problem asks for the output, "no sequence."
Sophisticated, insightful solutions might well be possible. But, this is contest programming. It is often brain's work -- and often not. In practice, an exhaustive seach, by generating all permutations and pruning invalid sequences, is fast enough -- and quick and easy to code.
// This is pseudo-code -- not compilable code void antiprime (int start, int finish, int substringLength) { /* * Initialize array that will hold permutations */ int sequenceSize = finish - start + 1; int sequence [sequenceSize]; for (int i=start; i <= finish; i++) { sequence[i-start] = start; /* * Call helper to find antiprime permutation. Print * permutation, or error */ if (search (sequence, substringLength, 0) println (sequence); else println ("no sequence"); } boolean search (int sequence[], int substringLength, int start) { // We can't do anything if the sequence breaks before we start // our swaps if (!composite (sequence, substringLength, start) ) return false; // If we verify everything through the end, we won. if (start = sequence.length) return true; // Assumes that everything is good until start, // so it starts swapping it with successors to generate // permutations for (int i=start; i < sequence.length; i++) { sequence.swap (start, i); // swap to form new permutation // If it works, hurray! if (search (sequence, substringLength, start+1) // begin with rhs at new posn return true; //Otherwise undo, so we can loop to top, try again. sequence.swap (start, i); } return false; } boolean composite (int sequence[], int substringLength, int start) { if (start == 0) return true; int sum = sequence[start-1] for (int i=start-2; i > max(start-substringLength, 0); i--) { sum += sequence[i]; // notice: you need to write this, perhaps using a,lookup table. if (prime(sum)) return false; } return false; }
- Problem C
This is a simulation problem. It asks to simulate a game involving a deck of 52 card, 4 each of 13 types. Type 1, 2, 3, 4, 5, 6, ..., 13. In either game, the deck is initially shuffled.
In the case of either game, the goal is to determine if the game never ends (infinite loop), or, if it ends, the last card discarded by each player.
In the one player game, the player goes through the deck, counting each card, 1, 2, 3, 4, 5, 6, ..., 13, repeat. Each card is counted. If the card's count matches its type, it is removed. Otherwise, it is moved to the bottom of the deck.
The multiplayer game begins with the first player holding all cards. Instead of discarding a card, it is passed to the next player and becomes part of that player's deck. The last player actually gets to discard the cards. Players with no cards skip the move.
This is a brute force simulation. The trick is, of course, to perform the simulation -- without actually infinitely looping. The game will only infinitely loop if the first player (lowest number who still has cards) infinitely loops. That is because this player can hand off cards to other players breaking any loops downstream.
The lowest number player is in an infinite loop iff the the number of cards in the player's deck is divisible by 13 and no cards have been removed from this deck within the last pass through the deck. Both conditions must be true.
The first condition guarantees that, without removing any cards, the counted number assigned to each card can't change. If the cards are a multiple of 13 and we're doing arithmetic module 13 it just starts over each time. The second condition guarantees that no cards can be removed. Since the next presentation of the deck will be in the same order and counted the same way, if nothing could be removed last time, nothing changed -- and never will.
- Problem F
This problem considers permutations of letters (A, B, C, D, and E). It defines the distance between any pair of permutations to be the number of corresponding postions within the permutation that have different values. Input to the problem is a list of permutations. For a given input, we are asked to find a permutation (not necessarily from the input list) with the minimum total distance to the given input permutations. If there are several permutations with the same minimum distance, we are asked to return the lexicographically first among them.
This problem can be solved by brute force. Generate an array of all permutations in lexicographical order. For exam permutation in the list, determine the sum of the distances to each of the given permutations. Find the first permutation with the minimum distance.
A constant-factor optimization is to pre-compute the table of all distances. This mitigates the need to compute them multiple times. Also, replace the letters with corresponding numbers for fast access, e.g., A=1, B=2, C=3, D=4, E=5.
- Problem G
This problem defines an encoding for strings. A string is written in column-major order into a matrix of a given size. They are then read out in row-major order. The only trick is that when the matrix is read, it is alternately read from left-to-right and from right-to-left. For example:
Haveaniceday
H > E > I > D H E I D ---| / / / | A / A / C / A ===> |-- A A C A <--| ===> / / / | V / N / E / Y |-->V N E YHEIDACAAVNEY
The solution to this problem is just converting the row-major representation back into column-major representation. The alternating directions are just a nuisance. It is a brute force simulation.
An explicit matrix isn't needed. Instead, we just use nested loops.
void decrpyt (string cipherText, int numCols) { int numRows = cipherText.length / numCols; for (int row=0; row < numRows; row++) { for (int col=0; col < numCols; col++) { // left-to-right for even rows, right-to-left for odd rows if ( (i/2) == 0) println (cipherText.charAt(row*numCols + col)); else println (cipherText.charAt((row+1)*numRows - (col+1))); } } }
In Class Programming
In class, we broke into teams and worked on the remaining problems from the 2004 ECNA -- those that were not correctly solved by our teasm and were not discussed in class.Problems were submitted using a temporary prodedure. This procedure will change as soon as our official course space is created. Until then, we are poaching:
- cd /afs/andrew/course/15/100-kesden/handin/CONTEST
- mkdir andrewid1-andrewid2-andrewid3
where andrewid1, andrewid2, and andrewid3 are the andrewids of your team members
- cd andrewid1-andrewid2-andrewid3
- cp somefile .
where somefile is whatever you'd like to submit
- email gkesden@cs.cmu.edu to let him know that you have submitted something.
As teams submitted problems, we judged them and provided contest-style judging responses. The same set of problems was assigned as homework. Although the homework is an individual effort, you are free to use your own team's in-class work-product as a starting point for your own, individual effort.
Homework Submission
Until our course space is created, we'll submit homework as we did the in-class work, with three exceptions:
- Use only one andrewid, yours.
- No need to mail us to let us know that you have turned it in.
- We will not judge in real-time.
Important Note: The submission directory is now "/afs/andrew/course/15/295/handin".
The Most Important Thing
Remember -- we're here to help. Email us or drop by any time we can be of service.