Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

16 January 2022

Making Friends with Powers of 2

In the manga/anime series "Chihayafuru," the protagonist mentions that she "makes friends" with the universe of playing cards in her sport, Karuta, to help build a connection in hopes of achieving better results.

Note that all 2^n will be "the smallest positive even nth power," so I will omit from their descriptions. In combinatorics, 2^n is the size of the powerset for a size-N set.

1: self explanatory

2: self explanatory; the one and only even prime number; base 2/binary

4: self explanatory; all squares modulo 4 give residues 0 or 1 (prove it!); typical word size of a 32-bit Intel/AMD computer

8: number of ounces in a cup (go USA); typical word size of a 64-bit Intel/AMD computer; base 8/octal

16: 0xF + 1; base 16/hexadecimal; bits in a 2-byte integer

32: bits in a 4-byte integer; number of ounces in 1/4-gallon (smaller carton of milk)

64: bits in an 8-byte integer; number of ounces in half-gallon (larger carton of milk)

128: number of ounces in a gallon (jug of milk); amount of memory (in MB) in the first iPhone

256: 0xFF + 1; how much memory in MB my first Mac had

512: how much memory in MB my first computer had

1024: approximately interchangeable with 1000 = 10^3 for fast log2 and log10 computation; MB in a GB (or KB in a MB, or B in a KB, etc)

2048: seen in computer hardware flyers

4096: max memory (in MB) supported by 32-bit computers; seen in computer hardware flyers; pixel width of a true 4k screen

8192: seen in computer hardware flyers

16384: seen in computer hardware flyers

32768: one more than the largest positive value representable by a signed 16-bit integer

65536: bits in a 16-byte integer

131072: smallest power of 2 larger than 10^5 (common constant in programming problems)

262144: number of colours representable by a 18-bit panel when PC laptop makers were too cheap to use 24-bit panels in the 2000s; smallest power of 2 larger than 2*10^5 (common constant in programming problems)

[...]

1048576: smallest power of 2 greater than 10^6

[...]

16777216: number of colours representable by a 24-bit panel (before 10-bit "high colour" panels became popular)

[...]

1073741824: number of colours representable by a 30-bit panel (e.g. a "high colour" or "photography/video" panel); smallest power of 2 greater than 10^9

2147483648: one more than the largest positive value representable by a signed 32-bit integer

4294967296: number of different values representable by 32 bits

[...]

Something that starts with 9223 and has a lot of digits: 2^63 or 2^63-1, the latter is the largest positive value representable by a signed 64-bit integer

For these large values, e.g. 2^30 and up, I only know the first handful of digits, which is usually sufficient.

---

Writing these out, there are a few things that stand out to me:

  • much of these numbers are from computer-related activities, e.g. programming problems or browsing computer hardware brochures
  • the powers of 2 less than 100, especially those less than 10 (1, 2, 4), come up regularly for me in my day-to-day life

If you find special meaning to a power of two, leave a comment below! I may also write about other numbers, so stay tuned.

28 May 2021

Thought process on Codeforces 1353E

Link to problem

I remember seeing this problem almost a year ago during practice and getting stuck. Well, I finally solved it almost a year later!


Since the problem asks about equal spacing (of length K) between 1s in a binary string, I immediately  thought of numbers of 0s and 1s for each residue mod K. We have enough time to try each residue if the work for each residue is linearly proportional to the number of bits in each residue, i.e. K residues * (N / K) bits in each residue = O(N) total work. We know that while we are trying each residue, the bits at positions not congruent to K mod N have to be off, which is an easy precomputation done in O(N).

Our solution looks like this so far:

i64 solve(const i64 N, const i64 K, string_view S) {
  i64 total_on = 0;
  vec<i64> resid_on(K, 0);
  for (i64 i = 0; i < N; ++i) {
  	total_on += S[i] == '1';
    resid_on[i % K] += S[i] == '1';
  }

  i64 best = N;
  for (i64 resid = 0; resid < K; ++resid) {
    let i64 turn_non_resid_off = total_on - resid_on[resid];

    // gather the bits at resid (mod K) for convenience
    vec<bool> seq;
    for (i64 i = resid; i < N; i += K) {
      seq.push_back(S[i] == '1');
    }
    // TODO implement solve_resid
    let i64 subproblem_ans = solve_resid(seq);
    best = min(best, turn_non_resid_off + subproblem_ans);
  }
  return best;
}

Let's focus on bits at positions whose residues are resid (mod K). We know that they have to form the pattern 0*1*0*, i.e. 1111, 00111, 1000, 0000, 010, etc. So we have reduced the main problem to this problem. How do we solve it?

At first, my smooth brain thought this could be done with greedy. My approach was ignore leading 0s, ignore the 1s after the leading 0s. At the same time, ignore trailing 0s, and ignore the 1s before trailing 0s. Now we want to turn all the bits on in the middle subsegment. However, this fails on a case like:

10 2

0001000100

Because we can simply turn off one of the 1s rather turn on the 3 zeros in the middle.

At this point, my mind turned to dynamic programming. I started building my recurrences as follows:

  1. Define f(pos) = minimum moves to make the suffix [pos, N) valid.
  2. Define g(pos, state: {0, 1}) = minimum moves to make suffix [pos, N) valid such that bit at pos is state. This implies:
    1. g(pos, 1) = (pos != 1) + (moves to make the suffix [pos + 1, N) have the pattern 1*0*)
    2. g(pos, 0) = (pos != 0) + f(pos + 1)
and we want to solve for f(0), which is min(g(pos, *)). The wildcard used here and below represents all possible values for that argument, i.e. {0, 1} here.

This looks promising, but what is (moves to make the suffix [pos + 1, N) have the pattern 1*0*)?? It looked suspiciously "nice", i.e. something that I might be able to turn into a recurrence. OK, let's try that:
  1. Define h(pos, state) = minimum moves to make the suffix [pos, N) have the pattern 1*0*. This implies:
    1. h(pos, 1) = (pos != 1) + min(h(pos + 1, *))
    2. h(pos, 0) = \sum_{i = pos}^N (i != 0)
Well that was nice indeed. Putting it together by plugging h into g, we have:
  1. g(pos, 1) = (pos != 1) + min(h(pos + 1, *))
  2. g(pos, 0) = (pos != 0) + f(pos + 1), as before
Implementation is straightforward:

i64 solve_resid(const vec<bool>& seq) {
  vec<vec<i64>> h(seq.size() + 1, vec<i64>(2));
  for (i64 i = seq.size() - 1; i >= 0; --i) {
    h[i][0] = seq[i] + h[i + 1][0];
    h[i][1] = !seq[i] + min(h[i + 1][0], h[i + 1][1]);
  }
  vec<vec<i64>> g(seq.size() + 1, vec<i64>(2));
  for (i64 i = seq.size() - 1; i >= 0; --i) {
  	g[i][0] =  seq[i] + min(g[i + 1][0], g[i + 1][1]);
    g[i][1] = !seq[i] + min(h[i + 1][0], h[i + 1][1]);
  }
  return min(g.front()[0], g.front()[1]);
}

Voila, Accepted!

19 January 2021

FLPoTD: Writing Checks

This problem was inspired by a puzzle on the 2021 Mystery Hunt:

Given a string S of characters from the latin alphabet (a-z), find the smallest nonnegative amount (dollars and cents) whose spelled-out representation contains S as a subsequence.

  • The dollar amount should not include 'and': 123 is "one hundred twenty three", not "one hundred and twenty three".
  • If the amount is less than $1.00 (one dollar and zero cents), then the dollar amount is omitted: $0.46 is "forty six cents"
  • The cents is always expressed: $46.00 is "forty six dollars and zero cents"
  • This table contains the names of powers of 10: https://en.wikipedia.org/wiki/Power_of_10#Positive_powers
Follow-up:
  • Given a list of strings, all of which use exclusively lowercase letters, find the most expensive word.

06 September 2020

Codeforces Round #668 (Div. 2) Postmortem

Trying a different format with problem-by-problem breakdowns, which hopefully will be more useful when I review my postmortems. I will also backfill the postmortems.

A: I first implemented the metric computation (sort sums of adjacent elements) without having any idea of how to create a different permutation to achieve the same metric. Then I realized that reversing the input preserves the adjacent sums. I won't complain about a 0:02 submission.

B: If the first nonzero element were negative, then we have to incur cost equal to its magnitude. I thought this was it, but some sample inputs didn't pass. Then I noticed that if the first nonzero element were positive, we can add the magnitude to negative terms later in the array, which existed because of the constraint that all elements sum to 0 and that the sum stays 0 after every operation. I quickly patched my solution to keep a reserve from the magnitude of positive elements and using the reserve to decrease the cost of negative elements before incurring their costs. Again, not complaining about a 0:08 submission.

C: This is where I started to struggle. Having the same number of 0s as 1s in every K subarray is the same as the subarray sum equal to K/2. I got hung up on the "system of equations" representation, where every question mark was a variable (question mark at i would be a_i), which seemed extremely hard to solve. Then I thought that I can just look at the number of equations and the number of variables, which seemed sketchy, but I YOLO-ed and incurred a WA.

At about the 0:55 mark, I realized that the system of equations implied that a_i = a_j for i = j mod K. Facepalm. I quickly coded this solution for a 1:02 submission. RIP

D: Clearly, if B is at most DA away from A, then A can reach B in the first move and win. From the problem statement, I inferred that their 10^100 limit meant that either A will reach B in finite time, or B can always move to stay DA away from A. I quickly realized that the best chance of B dodging A is on the diameter of the tree, since in no other scenario will B have the space to run away from A. This implied that if A can go from the center(s) to the ends of the diameter in DA, then A can catch B.

I made the assumption that if DA > DB (it seemed intuitive), then A will be able to reach B eventually because B will run out of space. Thinking that these three criteria were sufficient, I submitted at 1:31 for my first WA on D. Given feedback that my solution failed pretest 2, I came up with the following counterexample to my solution:

1---2---3---4---5---6
        ^           ^
        A           B
DA = 2, DB = 3

My solution would incorrectly say that A never reaches B, while after two moves, A will first move from 3 to 5 and B will move from 6 to 3, letting A move from 5 to 3 on the third move. This really tripped me up, leading me to think about the relative difference between DA and DB. I tried that if the relative difference is larger than the half the diameter of the tree, then A cannot catch B, for another WA at 1:44. Finally, as a last ditch attempt, I thought that if DB is twice or more than DA and if the diameter is greater than twice DA, then A cannot catch B, for my third and final WA at 1:59.

25 June 2020

Educational Codeforces Round 90 Postmortem

What went well
  • B, C, D took me 5 minutes, 9 minutes, and 24 minutes respectively.
What went poorly
  • A took 21 minutes (drew a huge blank; no, I did not wake up late), which was a massive penalty, since it increases the penalties for B, C, and D.
  • Couldn't get E in an hour.
Where I got lucky
  • D was on the easier side.

23 June 2020

Codeforces Round #652 (Div. 2) Postmortem

What went well
  • 0:00 submission for A!
  • Got ABCD (albeit slowly).
What went poorly
  • Incurred one wrong answer for B and C each (and took a long time too).
  • Took half an hour to recognize the recursion for the tree structure in D.
  • Thought that the residues of numbers compare the same way as the the numbers themselves (i.e. a > b implies (a % m) > (b % m)…no, just no).
Where I got lucky
  • Got D with 9 minutes to go, which involved a slight wild guess.

21 June 2020

Codeforces Round #651 (Div. 2) Postmortem

What went well
  • Decently fast ABC (0:01, 0:10, 0:23).
What went wrong
  • Walled by D. I didn't recognize that binary search works because I didn't know how to verify that a subsequence with max less than or equal to a target value was doable in O(N).
  • I thought incorrectly (yet again) that O(N lg N) is too slow for N <= 10**6, so I tried something silly to find the minimum # subsequences of the form (01)+ or (10)+, when a straightforward O(N lg N) would have worked.
Where I got lucky
  • Gained rating!

13 June 2020

Bugforces (ft. senile logarithmic exponentiation)

Sad things happen when you lose your mind:
using i64 = long long;

// Precondition: (base, exponent) won't cause overflow
i64
my_pow(const i64 base, const i64 exponent)
{
  i64 accumulator = 0;
  for (i64 exponent2 = exponent, current_power = base; exponent2 > 0;
      exponent2 >>= 1, current_pow *= current_pow)
  {
    if (exponent2 & 1)
    {
      accumulator += current_power;
    }
  }
  return accumulator;
}

04 June 2020

Codeforces Round #647 (Div. 2) Postmortem

What went well
  • Got B in 5 minutes -- straightforward problem.
  • Got C in 5 minutes -- another straightforward problem.
  • Got D in 20 minutes, once I figured out how to read. :-P
  • New personal best!!!
What went poorly
  • Wrote an extremely clumsy solution for A because I decided after a brief tradeoff analysis to match the problem statement as close as possible instead of thinking about a better implementation
  • IlliterateForces strikes again -- misread D, so submitted a solution that solves a similar problem, which cost ~20min and a submission penalty.
  • Couldn't get E.
Where I got lucky
  • Got extremely lucky with B and C. I have been struggling with Bs recently; this B's 5 minute solve time thankfully bucks the trend. My previous fastest C solve time was ~15 minutes.

26 April 2020

Bugforces (ft Education Codeforces Round 86)

Consider the following implementation to find the smallest multiple of x at least as big as y:

typedef long long i64;

i64
next_mult_ge(const i64 y, const i64 x)
{
    return ceil(y / (double) x) * x;
}

What can go wrong?

Educational Codeforces Round 86 Postmortem

What went well
  • Sampling other submissions, my solution to C is cleaner and more intuitive (if only it worked!!!)
What went poorly
  • A took a long time
  • My C implementation died to nuances of ceil(); I wanted to find the multiple of a number at least as large as some threshold.
Where I got lucky
  • I was able to use fast-slow testing to determine a test case that breaks my C.

25 April 2020

TopCoder SRM 784 Postmortem

What went well
  • I was able to solve Div2C/Div1A.
  • I was able to fix a bug in my implementation for C.
What went poorly
  • Got A wrong because I couldn't understand the problem.
  • I realized the bug in my C after submitting my code, so the resubmission incurred a significant (200pt) penalty).
Where I got lucky
  •  Div2C/Div1A seemed a lot easier than usual.

14 April 2020

Codeforces Round #635 (Div. 2) Postmortem

What went well
  • A was trivial.
  • B was easy.
  • I didn't feel stuck when attempting D.
What went poorly
  • C took a long time because I didn't realize that I had to consider two criteria for the greedy solution (first Wrong Answer). Then I didn't realize that the two criteria were not mutually exclusive (second Wrong Answer). All said and done, I spent 1h30.
Where I got lucky
  • Somehow still above 1700!

03 March 2020

Codeforces Ozon Tech Challenge (Div. 1 + Div. 2) Postmortem

What went well
  • C was a silly number theory problem, which I got in ~10 minutes. This might be my fastest C solve yet.
What went wrong
  • I was unable to debug my solution to D in ~2 hours. I initially started pursuing an idea which involved querying vertices from pairs of vertices, but then I wasn't sure which two of the (up to) four vertices I should query. Then I noticed that I can just "trim the tree" by querying two leaves at once and pruning the tree if the LCA were neither leaves queried. However, I overcomplicated my solution by removing the vertices in the path from the leaf to the LCA, so I'm sure the bug is somewhere in there.
  • I wasn't sure how to prove B, which took me 20 minutes to solve.
Where I got lucky
  • I gained elo so my rating is back above 1700. Yay!

30 January 2020

Educational Codeforces Round 81 Postmortem

What went well
  • If I had actually solved 4 problems, I would have gained over 100 elo…
  • I solved D, which was a number theory problem. (Nick missed this problem; muahahaha)
What went wrong
  • My impure thoughts on problem B died on system test #11. Since I passed all 10 pretests, I didn't think to revisit my solution and verify with fast-slow testing.
  • I took a long time to recognize the totient function. My first reaction was "some inclusion-exclusion counting" upon realizing a pattern from my scratch work. Then I needed two tries to code a fast (sqrt N) totient function.
 Where I got lucky
  • Everyone else died on B. Turns out it is rated 1800.

19 January 2020

Codeforces Round #614 (Div. 2) Postmortem

What went well
  • A was a fast 3-minute problem.
  • B was easy and I solved it decently quickly in 9 minutes.
What went wrong
  • C took way too long to code, mainly because I didn't have a good representation on blocking either diagonal path. Turns out that if cell (r, c) is blocked, then I just needed to check (1-r, c-1), (1-r, c), and (1-r, c+1). Furthermore, if I used a 2x(N+2) grid instead of 2xN grid, then I wouldn't have to do any bounds checking because the (*, -1) and (*, N) cells could never be set.
Where I got lucky
  •  I didn't do so terribly so as to get demoted to green.

16 January 2020

TopCoder SRM 775 (Div. 2) Postmortem

What went well
  • My TopCoder career is off to a good start! I solved A and B for +199 rating, which puts me midway through blue (1300-1499).
What went wrong
  • I apparently failed to understand A, so wasted 5 minutes implementing something else.
  • I was rusty on floodfill and took 40 minutes to solve B, where 30 minutes was debugging.
Where I got lucky
  • I'm decently quick on easy problems, which luckily A and B were.

14 January 2020

Educational Codeforces Round 80 Postmortem

What went well
  • The contest was so hard that I really wanted to quit after reading A, but persevered to the end. Somehow I solved three problems…
What went poorly
  • I'm terrible with floors and ceilings. I'm also apparently bad at quickly recognizing monotonic functions.
  • I'm also bad at bijecting problems; I did not recognize during the contest that C was asking for a monotonically increasing sequence of length 2n, rather than two sequences of length n with weird constraints on elements.
Where I got lucky
  • Solving three problems with a high penalty only cost 8 elo.

10 January 2020

Codeforces Round #613 (Div. 2) Postmortem

Link to contest here

What went well
  • I found a most elegant solution to B: much more elegant than the editorial.
  • I figured out D, a bits (bitmasks?) problem! I think I'm really weak at bits and xors.
What went wrong
  • B took ~50 minutes and two wrong submissions.
Where I got lucky
  • I had to think a little for A to prove correctness, but end-to-end latency still only took 3 minutes.
  • I submitted C without proving correctness and got it right??! Lucky me!

05 January 2020

Codeforces Round #612 (Div. 2) Postmortem

Link to contest here

What went well
  • I know how DP works?!
What went wrong
  • I failed to read B correctly the first time. The problem is about Set (the game), which I have only played once, over 10 years ago. I thought that all features had to be same or different, instead of each feature being independent of each other.
  • I took an hour to debug C because I wasn't confident in my dp transition function. Amusingly, my initial state was wrong, which luckily triggered an assert I left in my code, quickly highlighting an issue.
Where I got lucky
  •  Somehow placed in the top 5% with just ABC???!