We teach wrong

Revision en1, by purplesyringa, 2024-09-27 18:49:04

When I was a child, I knew software development well (though not much of C++), and my parents motivated me to try out competitive programming.

The first thing I tried was dkirienko's section. In the first 20 minutes, GCD and the Euclidian algorithm were explained, the complexity of the Euclidian algorithm was proven, and that was pretty much it. We were then asked to solve a problem set on Codeforces individually. I didn't understand much from the complexity proof, couldn't solve a single problem, failed to figure out what I was supposed to do, and left crying. So yeah, stuff like that doesn't work.

I understood simpler topics, though. I knew basic math, like how to solve linear systems and quadratic equations, and could make simple observations, so the school stage of ROI was quite easy to get through. (The hardest part was to explain to my then-informatics teacher that yes, I want the adult problems.) During the municipal stage, I had 3 hours to solve what I'd classify as a 5-task Div 3 contest. I solved the first three and stumbled on D2B and D2C. That was still a good result for my age, so I was invited to a Moscow training camp.

My experience at the camp was a bit haphazard. I didn't understand the relative complexity of the topics. Which group was I supposed to join? Was it linear and stack algorithms, segment trees, or HLD? I knew what a stack is, I wrote a stack-based parser a month prior. With some help, I figured out I should join the former group anyway.

That day, I was taught how prefix sums work, two pointers, and some stack tricks in 1.5 hours. During this time, we discussed several problems, solved them together with other students (there were about two dozen of us), explained stuff to each other, and took notes. We then spent two or three hours solving problems individually. I didn't understand all the information, but I could apply some of it to the problems. That worked.


I don't want to compare the two groups; I'm pretty sure they all had their highs and lows. I want to compare the experiences.

The way we teach newcomers at Codeforces is closer to my first experience. We tell people to solve more problems, and our only advice when that doesn't work is to read editorials. When the problems require algorithms, we tell them to read an article or two on binary search, and then come back.

Respectfully, you are all morons.

This is not how you teach competitive programming.

What got me from crying at hearing "competitive programming" to loving it was the exact opposite of your advice.

  • I did not focus on ideas, I focused on algorithms.
  • I did not try to solve a problem and then read the editorial when I failed, it was the opposite in 50% of cases.
  • I did not go from idea-based problems to algorithmic problems as I obtained skill, it was vice versa.
  • "Go solve more problems" only helped me when I had high skill, not low skill.

I hope I've explained these points below.

Start with algorithms, not ideas

Algorithms and data structures have substance. They are concrete, finished products. Compared to ideas, their limited scope:

  • Makes them easier to teach, because you only have to convey a particular approach rather than a widely generalized notion,
  • Makes them easier to learn, because all people have very similar views on what the topic is, so you can find a source that works best for you without worrying about losing something precious,
  • Helps the teacher and the student connect because it is very clear whether both are on the same page.

Oh, but wouldn't the student not learn anything and globally applicable useful from this? That's not how it works. Your goal as a teacher is to explain the intuition rather than the algorithm itself. Instead of explaining the "what", explain the "why". If you do that, the little tricks you use to build the intuition and the intuition itself will transfer to other areas.

For example, suppose you need to give an online or an offline lecture on segment trees. You start like this:

Hi everyone! Today, we're going to take a look at a data structure called "Segment Tree".

A segment tree is like a linear array, so you can get an element by its index and modify it by index. However, in addition, it supports "range queries" of kind "what is the sum of all elements at indices between l and r":

  • Get element by index (return a[i])
  • Set element by index (a[i] = x)
  • Sum element at range (a[l] + a[l + 1] + ... + a[r])

Here's how not to continue:

This structure handles all these queries in $$$O(\log n)$$$ time.

Instead, you do this:

How would you handle these queries? Say you didn't need to care about performance, what's the easiest way you can imagine?

Someone says to just use an array.

That's right, we can just use an array. The first two queries will be handled in $$$O(1)$$$, but the third one is much slower, taking $$$O(n)$$$.

You draw a table with three rows and one column. The rows are named, "get", "set", and "range". The column is named "array". Complexity is written in the cells.

So this approach works fine if there are few calls of the third kind. What if that's not the case, but there were no queries of the second kind? No modifications whatsoever. You just need to sum successive elements of a fixed array. How would you solve this?

Someone says to use prefix sums. You add another column to the table, filling in rows 1 and 3.

That's right, prefix sums work in the absence of modifications. What if there were very few modifications, though? In this case, we can simply rebuild the prefix sum from scratch upon each modification in linear time.

You write $$$O(n)$$$ in row 2.

So we are clearly making tradeoffs here. Either setting is very fast and range queries are very slow or vice versa. But it turns out there's a good balance in between. The trick we'll work out in the next 15 minutes will get us to this:

Adds another column, titled "segment tree", writing $$$O(1), O(\log n), O(\log n)$$$ to the cells.

Some of you might think this is just a wall of useless text. But.

  • It brings people up to speed, quickly mentioning something they (supposedly) already know, reducing the steepness of the learning curve. They aren't just thrown into the topic, they have time to recall related notions before hearing new material. This is critical in real-time conversations.
  • It introduces the notion of tradeoffs. Space-time tradeoffs are easy to google; tradeoffs between different queries are rarely mentioned anywhere. Beginners need to realize that there isn't one option that fits all use cases, and it's fine to choose a solution based on input limits.
  • It introduces rebuilding a structure as a viable method to handle updates. This will come in handy later when sqrt decomposition is described. Acknowledging this trick early demonstrates that it's universal, and gives participants some edge.

I could go on and give examples of how to structure written posts to utilize this approach, but basically, what I'm trying to say is that teaching algorithms does not preclude teaching useful ideas and intuition. On the opposite, it simplifies the latter, because algorithms are something you can focus on and get generic ideas from, while "just solve problems" does not give newbies the right direction.

Editorial goes first

This one's admittedly clickbaity.

During the lecture, we discussed a certain topic. We then solved a contest composed mostly of problems based on this topic. So even before reading the problem, I knew what approach I was supposed to use. I then had to find the right way to apply the algorithm/data structure to the problem, and -- guess what -- that requires thinking, the skill you all say algorithms don't help to achieve.

About 20% of the problems were something we discussed and solved theoretically during the lecture. These problems trained my implementation skills. This is something beginners often lack, and, again, many of you argue algorithms don't help train that. But I think you're forgetting that implementing the algorithms themselves trains those skills, and these skills transfer to purely idea-based problems. Hell, if I got basic implementation skills from industrial programming, they can't be that unique!

These problems let me just copy-paste my code to other problems from the same contest. No "implementing the algorithm takes 70% of the time, this isn't an interesting problem" bullshit. It was the best of both worlds.

Keep to algorithmic problems, switch to idea-based problems after gaining skill

I only had success with the "go solve more problems" approach after failing at the regional stage of ROI twice. (Though the second one wasn't that much of a failure.) I don't know if the third time's a charm or what caused me to succeed the third time, but it certainly wasn't due to how much time I spent writing contests.

During this time, I studied complex topics like number theory, Knuth's optimization, Mo's algorithm, and flows. Some of them helped me directly. Others helped me cut corners: instead of inventing a cool linear solution, I'd use a segment tree on a complicated monoid.

But many of those topics helped me indirectly. My experience in utilizing data structures helped me learn decomposition. Over time, I developed a slow yet reliable way to solve a certain kind of problems. I'd implement the stupidest solution possible and then try to fit it into TL. To do this, I looked for the bottleneck, reduced it to a single computation (preferably not related to the problem at hand), and replaced it with an alternative way to compute the same value. This could be as simple as "compute the sum via a segment tree" or as hard as "use five linear passes".

In a roundabout way, solving algorithmic problems gave me tools to solve idea-based problems I struggled to solve before because the right tool was often clear in the former case, but always muddy in the second one, until I excelled in the tool.

"Go solve more problems" is for aces

I had to solve hundreds of problems independent of the algorithms because there was nothing practical to learn anymore. With segment trees, there's a high variety of problems waiting to be approached, revealing dozens of different globally applicable approaches. With the Gomory-Hu tree, not so much.

To me, the worst part about just taking part in contests was how tiring it was. It squeezed me out like nothing else did. There was little to discuss with friends. There was no time to rest. There was no feeling of progress. Understanding an algorithm you've heard of half a year ago feels great; solving one more problem on average, not so much. If you learned an algorithm but didn't manage to apply it, you still know one more algorithm than before; if you didn't solve a problem, you didn't get anything positive out of the contest.

I wasn't prepared for learning to be this hard. This demotivated me a lot. I was knowledgeable enough to realize I was good enough to push through, but I can't imagine myself starting my competitive programming career like this, with no sense of progress and no end in sight. And now we advise newcomers to start with this?

We need to do better

We have other problems with teaching. One problem I feel like I should call out is toxicity. A person I won't name (but you all will understand who I'm talking about anyway) uses a condescending tone like this:

...we all know how to read, we have our whooping 2 month of experience! Oh, my sweet summer child, my experiments show that many people with kinda cool achievements like medals on ROI don't know how to read statements. But don't worry, I'll teach you.

...in the first damn post in the Catalog. Now, I'm not saying it's their fault for it being the first post, but do we actually want to greet beginners like that in our knowledge base?


I don't have a definite learning plan that'll get you to red reliably. For all intents and purposes, "Go solve more problems" is the best we have now, even if it's wrong and there's theoretically a much better way. I don't have time nor energy to push against the grain and prepare materials the right way, alone. The best I can hope for is to start a discussion. Grab your popcorn.

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en1 English purplesyringa 2024-09-27 18:49:04 12535 Initial revision (published)