08. What is computational thinking — Homework Solutions
Problems 1 to 3 are done on paper. The .cpp files for
problem 4 and the challenge are in
exercises/08/homework/solutions/.
Open this page after trying on your own. For the paper problems, write your answer first, then compare — reading someone else's answer feels clear even when you could not necessarily reproduce it yourself. The Tests section holds checked inputs and outputs for the coding problems.
Running one of the answers:
g++ -std=c++17 -O2 -o jawab exercises/08/homework/solutions/04-penumpang.cpp
./jawab
Problem 1 — Abstraction
Problem. Rewrite three story problems as one "given ... print ..." sentence.
How to think about it. Discard the names of characters, objects, and units. Keep: what is given (the shape of the data) and what is asked (the shape of the answer).
Answer.
a. Given n numbers, print their total minus their
largest value.
b. Given n pairs of numbers naik[i] and
turun[i], print the largest value ever reached by the
running sum (naik − turun) after each pair is
processed.
c. Given n, print how many of the numbers 1 to
n have an odd number of divisors.
Form (c) is deliberately still halfway: "the switch is pressed once for each divisor" is the result of the first abstraction. The second abstraction — that an odd number of divisors only happens for square numbers — is done in the challenge.
Common mistakes.
- Keeping the story ("chicken", "bus stop") in the new sentence. If nouns from the story are still there, the abstraction is not finished.
- Answering with code. At this stage the answer is still a sentence.
Problem 2 — Decomposition
Problem. Break the bus problem into steps.
How to think about it. Each step must be small enough to be written in a few lines of code, and clear enough to be tested on its own.
Answer.
- Read
n. - Set up two accumulators:
di_dalam(current passengers, literally "inside") andterbanyak(the highest record, literally "most"), both with value 0. - Repeat
ntimes: read a pairnaikandturun. - At each stop: subtract
turunfromdi_dalam, then addnaik— in the order the problem states. - Compare
di_dalamwithterbanyak; if it is larger, update the record. - After all stops, print
terbanyak.
Note that steps 4 and 5 are two already familiar patterns: accumulation and the running champion.
Common mistakes.
- Storing all the data in an array first. Allowed, but unnecessary: the answer can be computed while reading.
- Checking the record before updating
di_dalam, so the last stop is never counted.
Problem 3 — Pattern recognition
Answer.
a. "how many ... are divisible by 7" → loop + counter, with the
condition % 7 == 0.
b. "two numbers whose product is largest" → nested loops over all
pairs. If n is large, sort first and then
compare the product of the two largest with the product of the two
smallest (two large negative numbers can win).
c. "how many 7s among the digits of 1 to n" → a loop over
1..n that processes the digits inside it with
% 10 and / 10.
d. "the most contests without overlapping schedules" →
sort + greedy. This is the activity scheduling
problem covered in Chapter 12.
Common mistakes.
- For (b), immediately answering "take the two largest numbers". For data that may be negative, that answer is wrong.
Problem 4 — Bus passengers
Problem. Print the largest number of passengers ever inside the bus.
How to think about it. Follow the decomposition from problem 2. No array is needed: two variables are enough, because the record can be updated while reading.
Walkthrough.
int di_dalam = 0, terbanyak = 0;
for (int i = 0; i < n; i++) {
int naik, turun;
cin >> naik >> turun;
di_dalam -= turun;
di_dalam += naik;
if (di_dalam > terbanyak) terbanyak = di_dalam;
}
cout << terbanyak << "\n";Check with an example: 5 0 → 5, 3 2 → 6,
1 4 → 3. The record is 6.
Tests.
| Input | Output | Tracing |
|---|---|---|
1 / 10 0 |
10 |
0 → 10 |
2 / 3 0 / 1 3 |
3 |
0 → 3 → 1 |
3 / 5 0 / 3 2 /
0 6 |
6 |
0 → 5 → 6 → 0 |
Each input line contains naik then turun
for one stop. The third row is the most important check: the largest
count (6) occurs in the middle of the trip, not at the
last stop. A program that only prints the bus's occupancy at the end
will answer 0.
Common mistakes.
- Letting passengers on first and then off, so the record is too large.
- Printing
di_dalamat the end instead ofterbanyak.
Challenge — Lamps
Problem. Count the lamps that are on, then find the pattern.
How to think about it. The direct version is simple:
for each i, flip the state of all its multiples. The amount
of work is n/1 + n/2 + n/3 + ... ≈ n ln n, so for
n = 1,000,000 it still finishes quickly.
Walkthrough.
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j += i)
nyala[j] = !nyala[j];
int banyak = 0;
for (int i = 1; i <= n; i++) if (nyala[i]) banyak++;
cout << banyak << "\n";The results:
n |
answer |
|---|---|
| 100 | 10 |
| 1000 | 31 |
| 10000 | 100 |
The pattern: the answer is the integer part of √n.
Why. Lamp number x is pressed once for
each divisor of x. Divisors always come in pairs: if
a divides x, then so does x / a.
So the number of divisors is even — except when
a = x / a, that is, when x is a square number.
Only lamps with square numbers are pressed an odd number of times and
end up on. The number of square numbers from 1 to n is
⌊√n⌋.
Once that pattern is found, the whole program becomes one line:
cout << (int)sqrt((double)n) << "\n";This is the purest form of this chapter: observing a pattern replaces
computation. In OSN, the difference between an n log n
answer and an O(1) answer is often a one-sentence observation like
this.
Tests.
| Input | Output | ⌊√n⌋ |
|---|---|---|
1 |
1 |
1 |
100 |
10 |
10 |
1000 |
31 |
31 |
10000 |
100 |
100 |
That last column is the pattern. Once the pattern is found, the whole
program can be replaced by one line — and the difference is large: the
direct method takes about n log n switch presses, whereas
computing the square root takes one operation.
Common mistakes.
- Using
sqrtwithout care on large numbers. For very largen,(int)sqrt(...)can be off by one; check it withwhile ((long long)(akar + 1) * (akar + 1) <= n) akar++;. - Concluding a pattern from only one value of
n. Three data points at minimum, and the pattern still has to be proven with a reason.
OSN-style exercises — walkthrough
The answer files are in
exercises/08/osn/solutions/.
Problem 1 — Mango Harvest
The abstraction. Given N numbers and a
threshold K, run an accumulator that grows at each step and
is emptied each time it reaches K; print how many times it
was emptied and what remains.
The orchard, the market, and Pak Dengklek's laziness do not make it into the code.
How to think about it. The only things to remember
are the current warehouse contents and a sales counter. The
Aᵢ values do not all need to be stored: each one is used
once and then no longer needed. Realizing this turns the problem from
"an array of 200,000 elements" into "two variables".
Check the type: just before being emptied, the warehouse holds at
most K − 1 + Aᵢ, which can approach 2 × 10⁹ — beyond
int.
Walkthrough.
long long simpanan = 0;
int penjualan = 0;
for (int i = 0; i < n; i++) {
long long a;
cin >> a;
simpanan += a;
if (simpanan >= k) {
penjualan++;
simpanan = 0;
}
}
cout << penjualan << " " << simpanan << "\n";Tests.
| Input | Output | What it checks |
|---|---|---|
5 10 / 3 4 5 2 9 |
2 0 |
the sample |
3 100 / 1 2 3 |
0 6 |
never reaches the threshold |
2 1 / 0 0 |
0 0 |
zero harvest, warehouse stays empty |
The second case matters: the answer is not 0 0. The
remainder that was never sold must still be printed.
Common mistakes.
- Using
>instead of>=. The problem statement says "reachesKfruits or more". - Forgetting to empty the warehouse after a sale.
- Using
intforsimpanan("stock").
Problem 2 — New Record
The abstraction. Given a sequence of N
numbers, print how many numbers are smaller than all the numbers before
them.
How to think about it. Comparing each runner with
all previous runners takes N² = 4 × 10¹⁰ comparisons. But
"faster than all previous runners" means the same as "faster than the
fastest so far" — and that fastest time only needs to be kept in one
variable. The problem is solved in a single read.
The first runner is handled as a special case, because there is nothing to compare against yet.
The word faster must be read carefully: a time equal
to the record does not count, so the comparison is <,
not <=.
Walkthrough.
long long terbaik = 0;
int rekor = 0;
for (int i = 0; i < n; i++) {
long long t;
cin >> t;
if (i == 0 || t < terbaik) {
rekor++;
terbaik = t;
}
}
cout << rekor << "\n";Tests.
| Input | Output | What it checks |
|---|---|---|
6 / 15 12 14 10 10 9 |
4 |
the sample |
1 / 5 |
1 |
one runner |
4 / 7 7 7 7 |
1 |
equal time does not break it |
Common mistakes.
- Using
<=, so equal times are counted too and the third case answers4. - Setting
terbaik("best") to 0 and comparing right away, so the first runner is never counted. - Storing all the times in an array and then comparing pairwise.
Problem 3 — Rotating Candy
The abstraction. Given N and
M, print ((M − 1) mod N) + 1 and
⌊(M − 1) / N⌋ + 1.
How to think about it. Simulation is both the
correct answer and an impossible one: M can be 10¹⁸, while
one second is only enough for about 10⁸ steps. A constraint that large
always means the answer is a formula.
The pattern shows up when you number the candies. Candy 1 goes to
child 1, candy 2 to child 2, candy N to child
N, candy (N + 1) back to child
- So candy
mgoes to the child numbered "the remainder ofmdivided byN" — with one adjustment: a remainder of 0 means childN, not child 0.
That adjustment is written neatly as (m - 1) % n + 1:
first shift the numbering to 0 through N − 1, compute the
remainder, then shift back to 1 through N.
Child 1 receives candy 1, candy (N + 1), candy
(2N + 1), and so on. The count is
⌊(M − 1) / N⌋ + 1 — the same shift.
Walkthrough.
long long n, m;
cin >> n >> m;
long long terakhir = (m - 1) % n + 1;
long long bagianPertama = (m - 1) / n + 1;
cout << terakhir << " " << bagianPertama << "\n";Tests.
| Input | Output |
|---|---|
3 7 |
1 3 |
5 3 |
3 1 |
1 1000000000000000000 |
1 1000000000000000000 |
1000000000 1000000000000000000 |
1000000000 1000000000 |
The third row is the edge case N = 1: all the candy goes
to the same child. The fourth row makes sure large divisions stay
correct.
Common mistakes.
- Writing
m % nwithout the adjustment, so multiples ofNanswer0— a child number that does not exist. - Computing the first child's share as
m / n, which is wrong whenever there is a remainder. - Writing the simulation
for (long long i = 0; i < m; i++). Logically correct, but it will never finish.
If you are still stuck
- Story problems feel complicated → Chapter 08, the Abstraction section and the story → core table.
- You know the answer but not where to start → Chapter 08, the Decomposition section.
- Not sure your plan is fast enough → Chapter 08, the complete example from problem to plan, then Chapter 10.
The habit to build from now on: before writing code, first answer the five questions in the Checklist before writing code in Chapter 08.