CCCGuide
Math and Geometry (S1)

Coordinate geometry, rectangles, polygons

very-frequent

Geometry problems in the CCC are rarely about heavy math. Most of them come down to a few formulas, a decision about integers versus decimals, and careful counting. This module builds that toolkit, and then uses it on three Senior S1 problems.

Points, distances, and slopes

A point is a pair (x, y). For two points (x1, y1) and (x2, y2), the horizontal and vertical differences are dx = x2 - x1 and dy = y2 - y1.

  • The distance between the points is the square root of dx*dx + dy*dy (the Pythagorean theorem).
  • The slope of the line through them is dy / dx: how much y changes for each 1 step in x.

When you only need to compare distances, compare the squared distances instead. There is no square root, so everything stays an exact integer.

long long dx=x2-x1;
long long dy=y2-y1;

long long dist2=dx*dx+dy*dy;      // squared distance, exact
double dist=sqrt((double)dist2);  // only if you need the real distance

Slopes are not only about geometry. If x is time and y is position, then the slope of a segment is a speed. Problem 2 below uses exactly that idea.

Areas of simple shapes

Almost every polygon area in a contest can be cut into three basic shapes:

  • Rectangle: width * height.
  • Triangle: base * height / 2.
  • Trapezoid (two parallel sides a and b, with distance h between them): h * (a + b) / 2.

So the strategy is to cut a complicated shape into these pieces, find each area, and add them up.

Those formulas contain a / 2, which tempts us into decimals. But the total is always a whole number or a whole number plus one half. So instead of computing the area directly, compute twice the area, which is always an integer, and only divide by 2 at the very end. Integer arithmetic is exact, so there are no rounding surprises.

S1 Walkthrough: Crazy Fencing

Crazy Fencing2021 · S1 · Original problem · DMOJ

You need to paint a wooden fence between your house and your neighbour’s house. You want to determine the area of the fence, in order to determine how much paint you will use.

However, the fence is made out of NN non-uniform pieces of wood, and your neighbour believes that they have an artistic flair. In particular, the pieces of wood may be of various widths. The bottom of each piece of wood will be horizontal, both sides will be vertical, but its top may be cut on an angle. Two such pieces of wood are shown below:

Two right trapezoids.

Thankfully, the fence has been constructed so that adjacent pieces of wood have the same height on the sides where they touch, which makes the fence more visually appealing.

The first line of the input will be a positive integer NN, where N≤10 000N \leq 10\,000.

The second line of input will contain N+1N+1 space-separated integers h1,…,hN+1h_1,\ldots, h_{N+1} (1≤hi≤100,1≤i≤N+1)(1 \le h_i \le 100, 1 \le i \le N+1) describing the left and right heights of each piece of wood. Specifically, the left height of the iith piece of wood is hih_i and the right height of the iith piece of wood is hi+1h_{i+1}.

The third line of input will contain NN space-separated integers wiw_i (1≤wi≤100,1≤i≤N)(1 \le w_i \le 100, 1 \le i \le N) describing the width of the iith piece of wood.

Output the total area of the fence.

3
2 3 6 2
4 1 1

Output for Sample Input 1

18.5

Explanation of Output for Sample Input 1

The fence looks like the following:

Three right trapezoids placed side by side, each with their bases placed vertically and the side forming the height placed horizontally at the bottom. The left piece has left base of length 2, right base of length 3, and height of length 4. The middle piece has left base 3, right base 6, and height 1. The right piece has left base 6, right base 2, and height 1. The left and middle pieces share the base of length 3, the right and middle pieces share the base of length 6.

When looking from left to right, the individual areas of the pieces of wood are 10=4⋅(2+3)/210 = 4\cdot (2+3)/2, 4.5=1⋅(3+6)/24.5 = 1\cdot(3+6)/2, and 4=1⋅(6+2)/24 = 1 \cdot (6+2)/2, for a total area of 18.5.

4
6 4 9 7 3
5 2 4 1

Output for Sample Input 2

75

Explanation of Output for Sample Input 2

The fence looks like the following:

Four right trapezoids placed side by side, each with bases placed vertically and the edge forming the height placed horizontally at the bottom. The left piece has left base of length 6, right base of length 4, and height of length 5. The middle-left piece has left base 4, right base 9, and height 2. The middle-right piece has left base 9, right base 7, and height 4. The right piece has left base 7, right base 3, and height 1. The left and middle-left pieces share the base of length 4, the middle-left and middle-right pieces share the base of length 9, the middle-right and right pieces share the base of length 7.

When looking from left to right, the individual areas of the pieces of wood are 25, 13, 32, and 5, for a total area of 75.

In other words:

  • The fence is N boards standing side by side. Board i has left height h[i], right height h[i+1], and width w[i].
  • Each board is a trapezoid. Its two parallel sides are the left and right edges (the heights), and the distance between them is the width of the board.
  • The answer is the sum of the areas of all the trapezoids.

Be careful with the word "height" here. In the trapezoid formula, h is the distance between the parallel sides, which for this fence is the board's width. So the area of board i is w[i] * (h[i] + h[i+1]) / 2.

Check it on Sample 1: the first board is 4 * (2 + 3) / 2 = 10, the second is 1 * (3 + 6) / 2 = 4.5, and the third is 1 * (6 + 2) / 2 = 4, for a total of 18.5.

Add up w[i] * (h[i] + h[i+1]) without the division. That gives twice the area, which is an integer. If it is even, print half of it. If it is odd, print half of it (rounded down) followed by .5.

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin>>n;

    vector<int> h(n+1), w(n);
    for(int i=0;i<=n;i++){
        cin>>h[i];
    }
    for(int i=0;i<n;i++){
        cin>>w[i];
    }

    long long twice=0; // twice the total area, always an integer
    for(int i=0;i<n;i++){
        twice+=(long long)w[i]*(h[i]+h[i+1]);
    }

    if(twice%2==0){
        cout<<twice/2;
    }else{
        cout<<twice/2<<".5";
    }
}

A double would also work here, but integer arithmetic prints exactly 18.5 and 75 with no rounding worries.

S1 Walkthrough: Surmising a Sprinter’s Speed

Surmising a Sprinter’s Speed2020 · S1 · Original problem · DMOJ

Trick E. Dingo is trying, as usual, to catch his nemesis the Street Sprinter. His past attempts using magnets, traps and explosives have failed miserably, so he’s catching his breath to gather observational data and learn more about how fast Street Sprinter is.

Trick E. Dingo and Street Sprinter both inhabit a single straight west-east road with a particularly famous rock on it known affectionately as The Origin. Positions on this straight road are measured numerically according to the distance from The Origin, and using negative numbers for positions west of The Origin and positive numbers for positions east of The Origin.

The observations by Trick E. Dingo each contain two numbers: a time, and the value of Street Sprinter’s position on the road at that time. Given this information, what speed must Street Sprinter must be capable of?

The first line contains a number 2≤N≤100 0002\le N \le 100\ 000, the number of observations that follow. The next NN lines each contain an integer 0≤T≤1 000 000 0000 \le T \le 1\ 000\ 000\ 000 indicating the time, in seconds, of when a measurement was made, and an integer −1 000 000 000≤X≤1 000 000 000-1\ 000\ 000\ 000 \le X \le 1\ 000\ 000\ 000 indicating the position, in metres, of the Street Sprinter at that time. No two lines will have the same value of TT.

For 7 of the 15 available marks, N≤1000N \le 1000.

Output a single number XX, such that we can conclude that Street Sprinter’s speed was at least XX metres/second at some point in time, and such that XX is as large as possible. If the correct answer is CC, the grader will view XX as correct if ∣X−C∣/C<10−5|X-C|/C < 10^{-5}.

3
0 100
20 50
10 120

Output for Sample Input 1

7.0

Explanation of Output for Sample Input 1

Since the Street Sprinter ran from position 100 to position 120 between time 0 and time 10, we know its speed must have been at least 22 at some point in time: if it was always less than 22, then the distance of 20 could not be covered in 10 seconds. Likewise, the speed must have been at least 7 in order to travel between position 120 and 50 in 10 seconds.

5
20 -5
0 -17
10 31
5 -3
30 11

Output for Sample Input 2

6.8

In other words:

  • We have N observations (T, X): at time T, the sprinter was at position X. They are not given in time order.
  • Between two observations, the sprinter covered |X2 - X1| metres in |T2 - T1| seconds. Their average speed over that stretch was |X2 - X1| / |T2 - T1|.
  • If the average over a stretch was that much, the sprinter must have reached at least that speed at some moment. We want the largest such value.

Think of each observation as a point (T, X) on a graph. The speed between two observations is the absolute value of the slope of the line between the two points.

The direct idea is to try every pair of observations and take the best speed. That takes O(N2)O(N^2) time. It is fine for the 7 marks with N up to 1000, but with N up to 100000 there are about 5 billion pairs, which is far too slow.

The key observation is that only neighbouring observations matter. Sort the observations by time. Take three observations a, b, c in time order. The distance from a to c is at most the distance from a to b plus the distance from b to c, so the speed over the long stretch a to c is a weighted average of the speeds over a to b and b to c. A weighted average can never beat the larger of the two. So a longer stretch never gives a bigger answer than the stretches inside it, and it is enough to check neighbouring pairs after sorting. That takes O(Nlog⁡N)O(N \log N).

Check it on Sample 2. Sorted by time, the observations are (0,-17), (5,-3), (10,31), (20,-5), (30,11). The speeds between neighbours are 14/5 = 2.8, 34/5 = 6.8, 36/10 = 3.6, and 16/10 = 1.6, so the answer is 6.8.

Two details to watch:

  • The differences between positions can reach 2 000 000 000, which is close to the limit of a 32-bit int (about 2.1 billion). Use long long in C++ to be safe. Python integers have no such limit.
  • The answer is checked with a relative error of 10−510^{-5}, so we need enough digits. The smallest possible nonzero answer is 1 metre in 1 000 000 000 seconds, which is 0.000000001. Printing only 6 decimals would turn that into 0.000000. Print 12 decimals to be safe. In Python, print(best) would show tiny values like 1e-09, so use a format string instead.
#include<bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin>>n;

    vector<pair<long long,long long>> p(n); // (time, position)
    for(int i=0;i<n;i++){
        cin>>p[i].first>>p[i].second;
    }

    sort(p.begin(),p.end()); // sort by time

    double best=0;
    for(int i=1;i<n;i++){
        long long dx=llabs(p[i].second-p[i-1].second);
        long long dt=p[i].first-p[i-1].first;
        best=max(best,(double)dx/dt);
    }

    cout<<fixed<<setprecision(12)<<best;
}

Counting perimeters

A counting trick works for any shape made of tiles. Suppose every tile has the same perimeter p. If we glue tiles together, the perimeter of the combined shape is:

(number of tiles) * p - 2 * (number of shared sides)

Why? Every tile counts all of its own sides. A side shared by two tiles is counted once by each of them, but it is inside the shape, so it is not part of the outer boundary. Removing it takes away 2.

For example, on a grid of squares each tile has 4 sides, so the perimeter of a group of squares is 4 * (tiles) - 2 * (shared sides).

This turns a geometry problem into counting: count the tiles, and count the pairs of neighbours.

J4/S1 Walkthrough: Trianglane

Trianglane2023 · S1 · Original problem · DMOJ

Bocchi the Builder just finished constructing her latest project: a laneway consisting of two rows of white equilateral triangular tiles. However, at the last moment, disaster struck! She accidentally spilled black paint on some of the tiles. Now, some of the tiles are wet and the other tiles are dry. Bocchi must place warning tape around the perimeters of all wet areas. Can you help her determine how many metres of tape she needs?

The first triangular tile will point upwards. Each pair of adjacent tiles (that is, tiles that share a common side) will point in opposite directions. Each tile has a side length of 1 metre.

The first line of input will consist of one positive integer CC, representing the number of columns.

The next two lines will each consist of CC integers separated by spaces. Each integer represents the colour of a tile along the laneway, with 1 indicating that the tile is black (wet) and 0 indicating that the tile is white (dry).

The following table shows how the available 15 marks are distributed:

MarksDescriptionBound
3The laneway is not very long, black tiles are never adjacent and the second row is fully white.C≤2 000C \le 2\,000
3The laneway is not very long, black tiles may be adjacent and the second row is fully white.C≤2 000C \le 2\,000
5The laneway is not very long, black tiles may be adjacent and may appear in the second row.C≤2 000C \le 2\,000
4The laneway may be very long, black tiles may be adjacent and may appear in the second row.C≤200 000C \le 200\,000

Output a single integer representing the length of tape Bocchi needs, in metres.

5
1 0 1 0 1
0 0 0 0 0 

Output for Sample Input 1

9

Explanation of Output for Sample Input 1

The tiles are painted as follows, creating three wet areas. Bocchi will need 9 metres of warning tape as shown in yellow.

Two rows of five equilateral triangle tiles. In the top row, the tiles are pointing up, down, up, down, up. In the bottom row, the tiles are pointing down, up, down, up, down. Each tile pointing upward in the top row shares a side with a tile pointing downward in the bottom row. Each tile pointing upward in the top row is outlined in yellow.

7
0 0 1 1 0 1 0
0 0 1 0 1 0 0

Output for Sample Input 2

11

Explanation of Output for Sample Input 2

The tiles are painted as follows, creating three wet areas. Bocchi will need 5 metres of warning tape to surround one area and 3 metres of warning tape to surround each of the other two areas as shown in yellow.

Two rows of seven tiles arranged as in the previous input. The third and fourth tiles in the top row (pointing up then down) and the third tile in the bottom row (pointing down) form a trapezoidal area outlined in yellow. Also, the sixth tile in the top row and the fifth tile in the bottom row are each outlined in yellow.

In other words:

  • The laneway is two rows of C triangular tiles. Black tiles are wet, and we need the total length of tape around all the wet areas.
  • Each triangle has 3 sides of length 1, so the formula is 3 * (black tiles) - 2 * (shared sides between two black tiles).

So we only need to know which pairs of tiles share a side. The tiles alternate between pointing up and pointing down, so:

  1. Two tiles next to each other in the same row always share a side.
  2. A tile in the top row that points up shares its bottom side with the tile directly below it in the second row, which points down. The top row starts with an up-pointing tile, so in code (counting from 0) these are the even columns 0, 2, 4, ....
  3. No other pairs share a side. A down-pointing tile in the top row has its flat side on the outside, and so does an up-pointing tile in the bottom row.

Check it on Sample 2. There are 5 black tiles, so 3 * 5 = 15. The shared sides are the two neighbouring black tiles in the top row (columns 2 and 3), and the pair in column 2 between the rows (an even column, black in both rows). That is 2 shared sides, so the tape needed is 15 - 2 * 2 = 11, which matches the expected output.

#include<bits/stdc++.h>
using namespace std;

int main(){
    int c;
    cin>>c;

    vector<int> a(c), b(c); // a = top row, b = bottom row
    for(int i=0;i<c;i++){
        cin>>a[i];
    }
    for(int i=0;i<c;i++){
        cin>>b[i];
    }

    long long ans=0;
    for(int i=0;i<c;i++){
        ans+=3*(a[i]+b[i]); // every black tile has 3 sides
    }

    for(int i=0;i+1<c;i++){
        ans-=2*(a[i]&a[i+1]); // shared side in the top row
        ans-=2*(b[i]&b[i+1]); // shared side in the bottom row
    }

    for(int i=0;i<c;i+=2){
        ans-=2*(a[i]&b[i]); // shared side between the rows
    }

    cout<<ans;
}

Here a[i] & a[i+1] is 1 only when both tiles are black, and 0 otherwise, so each shared side is subtracted exactly when both of its tiles are wet. The whole solution is one pass, O(C)O(C), so even C up to 200000 is easy.

More: polygons and the shoelace formula

Not every polygon cuts neatly into rectangles and trapezoids. For a polygon with vertices (x[0], y[0]), (x[1], y[1]), ... listed in order around the boundary (clockwise or counterclockwise), the shoelace formula gives its area:

twice area = | sum over i of ( x[i] * y[i+1] - x[i+1] * y[i] ) |

where the vertex after the last one is the first one again. It is written as twice the area on purpose: with integer corners, the sum is an exact integer, and we apply the same halving habit as before.

For example, the triangle (0,0), (4,0), (0,3) gives 0 + 12 + 0 = 12, so its area is 6.

long long twice=0;
for(int i=0;i<n;i++){
    int j=(i+1)%n; // next vertex, wrapping around
    twice+=x[i]*y[j]-x[j]*y[i];
}

twice=llabs(twice); // twice the area

Quick tips

  • Prefer integers. Doubling an area, comparing squared distances, and counting sides all stay exact.
  • When you do need decimals, read the checker rules. If the problem allows a relative error, print enough decimal places, and remember that very small answers need more of them.
  • Watch the sizes of your numbers. Differences and products of values around 10910^9 can leave the range of a 32-bit int, so use long long in C++.
  • For tile problems, count tiles and shared sides instead of tracing the outline.
  • Check your formula against every sample by hand before writing code.

On this page