Topcoder 250-Point problem from SRM 484, Division 2 (NumberMagicEasy)


Please review the CS302 lecture notes on bit operations if bit arithmetic is something with which you are not confident.

The easiest solution to this problem is to set up four sets, one for each card, and then simply try every potential answer from 1 to 16. However, I'm going to use bit arithmetic to give you some practice. As I have mentioned in the past, you may represent a set of up to 32 numbers with an integer (or up to 64 numbers with an unsigned long long). A number i is in the set represented by integer s if the i-th bit of s is set. In binary arithmetic:

So, what we're going to do is represent each card with an integer. For example, the first card will be equal to:

(1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8)

I'll end up with four cards, which I'll keep in a vector c. Now, what I want to do is take the intersection of four sets. If the answer to a question is 'Y', then I want the set in c. If the answer is no, then I want the set's complement. So, what I do first is go through the four answers to the questions, and if any answer is 'N', I'll set the relevant card to its binary negation using ˜. Then, I'll calculate the intersection of the four cards. The bit that is set in the result of the intersection is going to be the bit for the answer. Here's the code (NumberMagicEasy-NoPrint.cpp):

#include <string>
#include <vector>
#include <iostream>
using namespace std;

class NumberMagicEasy {
  public:
    int theNumber(string answer);
};

int NumberMagicEasy::theNumber(string answer)
{
  int i;
  int res;
  vector <int> c;

  c.push_back((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8));
  c.push_back((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 9) | (1 << 10) | (1 << 11) | (1 << 12));
  c.push_back((1 << 1) | (1 << 2) | (1 << 5) | (1 << 6) | (1 << 9) | (1 << 10) | (1 << 13) | (1 << 14));
  c.push_back((1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9) | (1 << 11) | (1 << 13) | (1 << 15));

  for (i = 0; i < 4; i++) if (answer[i] == 'N') c[i] = ~c[i];

  res = c[0] & c[1] & c[2] & c[3];

  for (i = 1; i <= 16; i++) if (res & (1 << i)) return i;

  return -1;  // This won't happen, but it shuts the compiler up and helps find bugs.
}

To test and run this, do the following in your own directory (besides having examples, if you give the program an argument of -1, you can enter the Y's and N's on standard input.)

UNIX> cp /home/plank/cs302/Notes/NumberMagicEasy/Main.cpp .
UNIX> cp /home/plank/cs302/Notes/NumberMagicEasy/NumberMagicEasy-NoPrint.cpp NumberMagicEasy.cpp
UNIX> g++ Main.cpp
UNIX> a.out 0
5
UNIX> a.out 1
8
UNIX> a.out 2
16
UNIX> a.out 3
1
UNIX> echo NNNN | a.out -1
16
UNIX> echo YYNN | a.out -1
4
UNIX> 
If this is confusing to you, I suggest that you try the program below that prints the bits and the hex.

After doing the bitwise-and of the four sets, I calculated the return value by seeing if each element from 1 to 16 is in the final set.

Also, think about the following -- what if the input is 'NNNN'? The intersection is going to have a bunch of bits set that are not in positions 1 through 16. Why? Think about card c[0]. Originally it was:

0000 0000 0000 0000 0000 0001 1111 1110 - 0x1fe

When I negate it, it turns into

1111 1111 1111 1111 1111 1110 0000 0001 - 0xfffffe01

If I do that to all of the cards, then the final result will have all of those upper ones set, plus bit 0 set. However, since my code to find the final value doesn't test those bits, I don't need to worry about it. It's just something you should keep in the back of your mind.


Printing the bits

Spurred by a CS302 student in 2015, I've added a second solution, which prints the hex and the bits. When I print the bits, I print them in three groups -- [31-17], [16-1] and [0]. The reason is that the only bits we care about are bits 1 through 16, which are in the middle column.

UNIX> cp /home/plank/cs302/Notes/NumberMagicEasy-Print.cpp NumberMagicEasy.cpp
UNIX> g++ Main.cpp
UNIX> a.out 0

Here are the sets after being set up:
    c[0]: 0x000001fe  000000000000000 0000000011111111 0
    c[1]: 0x00001e1e  000000000000000 0000111100001111 0
    c[2]: 0x00006666  000000000000000 0011001100110011 0
    c[3]: 0x0000aaaa  000000000000000 0101010101010101 0

Now let's print them after negating:
(Y) c[0]: 0x000001fe  000000000000000 0000000011111111 0
(N) c[1]: 0xffffe1e1  111111111111111 1111000011110000 1
(Y) c[2]: 0x00006666  000000000000000 0011001100110011 0
(Y) c[3]: 0x0000aaaa  000000000000000 0101010101010101 0


Here's the final result:
          0x00000020  000000000000000 0000000000010000 0

res has value 5
5
UNIX> a.out 1

Here are the sets after being set up:
    c[0]: 0x000001fe  000000000000000 0000000011111111 0
    c[1]: 0x00001e1e  000000000000000 0000111100001111 0
    c[2]: 0x00006666  000000000000000 0011001100110011 0
    c[3]: 0x0000aaaa  000000000000000 0101010101010101 0

Now let's print them after negating:
(Y) c[0]: 0x000001fe  000000000000000 0000000011111111 0
(N) c[1]: 0xffffe1e1  111111111111111 1111000011110000 1
(N) c[2]: 0xffff9999  111111111111111 1100110011001100 1
(N) c[3]: 0xffff5555  111111111111111 1010101010101010 1


Here's the final result:
          0x00000100  000000000000000 0000000010000000 0

res has value 8
8
UNIX> a.out 2

Here are the sets after being set up:
    c[0]: 0x000001fe  000000000000000 0000000011111111 0
    c[1]: 0x00001e1e  000000000000000 0000111100001111 0
    c[2]: 0x00006666  000000000000000 0011001100110011 0
    c[3]: 0x0000aaaa  000000000000000 0101010101010101 0

Now let's print them after negating:
(N) c[0]: 0xfffffe01  111111111111111 1111111100000000 1
(N) c[1]: 0xffffe1e1  111111111111111 1111000011110000 1
(N) c[2]: 0xffff9999  111111111111111 1100110011001100 1
(N) c[3]: 0xffff5555  111111111111111 1010101010101010 1


Here's the final result:
          0xffff0001  111111111111111 1000000000000000 1

res has value 16
16
UNIX> 

Fun with VI

Now, do you really think I typed in those push_back() statements? No, of course not. What I did was the following. In vi, I typed in the values that I wanted to be setting in the set:

1
2
3
4
5
6
7
8

Next, I put the cursor in the first line, and typed:

:,.+7s/\(.*\)/(1 << \1) |

Before I typed RETURN, I copied that command into my clipboard. Then, I typed return:

(1 << 1) |
(1 << 2) |
(1 << 3) |
(1 << 4) |
(1 << 5) |
(1 << 6) |
(1 << 7) |
(1 << 8) |

Nice -- now, a bunch of capital J's gives me:

(1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) |

It's pretty easy to put that into the format that you want. Then, for the next card, simply type in the next 8 numbers, and paste the command from the clipboard, and do a bunch of capital J's. We love vi.