- James S. Plank
- Original Lecture Notes: Mon Sep 9 14:21:00 EDT 2013
- Last Modification: Thu Sep 8 10:36:19 EDT 2016
- Problem description: http://community.topcoder.com/stat?c=problem_statement&pm=11129&rd=14237
- Skeleton implementation: Skeleton.cpp
- Main with examples: Main.cpp

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:

- An empty set is zero.
- You add
*i*to set*s*by setting*s*to*(s | (1 << i))*. - You test to see if
*i*is in set*s*by testing whether*(s & (1 << i))*is non-zero. - You may perform an intersection of two sets with bitwise and (&).
- You may perform a union of two sets with bitwise or (|).
- You may take a set's complement with bitwise not (˜)

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>If this is confusing to you, I suggest that you try the program below that prints the bits and the hex.cp /home/plank/cs302/Notes/NumberMagicEasy/Main.cpp .UNIX>cp /home/plank/cs302/Notes/NumberMagicEasy/NumberMagicEasy-NoPrint.cpp NumberMagicEasy.cppUNIX>g++ Main.cppUNIX>a.out 05 UNIX>a.out 18 UNIX>a.out 216 UNIX>a.out 31 UNIX>echo NNNN | a.out -116 UNIX>echo YYNN | a.out -14 UNIX>

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:

When I negate it, it turns into

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.

UNIX>cp /home/plank/cs302/Notes/NumberMagicEasy-Print.cpp NumberMagicEasy.cppUNIX>g++ Main.cppUNIX>a.out 0Here 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 1Here 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 2Here 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>

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.
**