<< problem 162 - Hexadecimal numbers | Numbers for which no three consecutive digits ... - problem 164 >> |

# Problem 163: Cross-hatched triangles

(see projecteuler.net/problem=163)

Consider an equilateral triangle in which straight lines are drawn from each vertex to the middle of the opposite side,

such as in the size 1 triangle in the sketch below.

Sixteen triangles of either different shape or size or orientation or location can now be observed in that triangle.

Using size 1 triangles as building blocks, larger triangles can be formed, such as the size 2 triangle in the above sketch.

One-hundred and four triangles of either different shape or size or orientation or location can now be observed in that size 2 triangle.

It can be observed that the size 2 triangle contains 4 size 1 triangle building blocks.

A size 3 triangle would contain 9 size 1 triangle building blocks and a size n triangle would thus contain n^2 size 1 triangle building blocks.

If we denote T(n) as the number of triangles present in a triangle of size n, then

T(1) = 16

T(2) = 104

Find T(36).

# My Algorithm

I failed to find an good scheme to enumerate all triangles but realized that the basic triangle of size 1 contains 6 lines;

the size 2 triangle contains 15 lines. These lines fall into six groups: they have a slope of 0, 30, 60, 90, 120 and 150 degrees.

I drew a few pictures and found that a size n triangle has 9n-3 lines. A size 36 triangle has 321 lines.

A brute-force search through all possible combinations of three lines checks:

- all three lines must not be parallel to each other

- they must not intersect at the same point

- all intersection points must be inside (or on the edge) of the outer-most triangle (the "hull")

Lines are created by converting two points (see struct `Point`

) on the line into the line equation ax + by = c, where

a = y_1 - y_2, b = x_2 - x_1 and c = x_2 y_1 - x_1 y_2 (see class `Line`

).

The intersection point of two lines is:

det = a_1 b_2 - a_2 b_1 → if zero, then both lines are parallel

x = (c_1 b_2 - c_2 b_1) / det

y = (a_1 c_2 - a_2 c_1) / det

The sign of the determinant of a line and a point tells me which side of the line the point is located.

When the three lines of a triangle are created in anti-clockwise order (AB, BC, CA - note: not AC !),

then the determinant must not be negative (see `insideHull`

).

There are {{321}choose{3}} = 5461280 potential triangles.

I can prune a few by observing that if two lines are parallel I can skip building a full triangle.

Moreover, if two lines intersect outside the hull, then I can skip checking the full triangle, too.

Pruning reduces the total number of full triangle to 1631449 (program becomes three times faster).

## Alternative Approaches

I found a pretty long and weird closed formula that finds the correct answer instantly.

I can't follow the proof, though, because I'm just a software engineer and not a mathematician.

## Note

All calculations have to consider a certain error margin (see `Epsilon`

) because `double`

can't exactly represently sqrt{3} (see `Height`

).

# My code

… was written in C++11 and can be compiled with G++, Clang++, Visual C++. You can download it, too.

#include <iostream>
#include <vector>
#include <cmath>
// allowed error, I could probably add a few zeros ...

const double Epsilon = 0.0000001;
// a simple 2D point

struct Point
{
// create new point
Point(double x_, double y_)
: x(x_), y(y_)
{}
// return true if two points are identical or very close (allow for error Epsilon)
bool operator==(const Point& other) const
{
return fabs(x - other.x) < Epsilon &&
fabs(y - other.y) < Epsilon; // faster Manhattan metric, not a true distance
}
double x;
double y;
};
// height in a triangle of size 1

const double Height = sqrt(3.0) / 2;
// points of the outer-most triangle, the "hull"
// note: below are the values for a size 1 triangle, they will be scaled accordingly in main()

Point A(0, 0);
Point B(1, 0);
Point C(0.5, Height);
// dummy point to indicate that two lines don't intersect (=> they are parallel)

const Point NoIntersection(9999999, 9999999);
// a line described by ax + by = c;

class Line
{
public:
// create new line through points "from" and "to"
Line(const Point& from, const Point& to)
{
// https://de.wikipedia.org/wiki/Koordinatenform
a = from.y - to.y;
b = to.x - from.x;
c = to.x*from.y - from.x*to.y;
}
// return intersection of the current line with a second line
Point intersect(const Line& other) const
{
// Cramer's Rule: https://en.wikipedia.org/wiki/Cramer%27s_rule
auto determinant = a * other.b - other.a * b;
// parallel ?
if (fabs(determinant) < Epsilon)
return NoIntersection;
auto x = (c * other.b - other.c * b) / determinant;
auto y = (a * other.c - other.a * c) / determinant;
return Point(x, y);
}
// return determinant
double determinant(const Point& p) const
{
return a * p.x + b * p.y - c;
}
private:
// line parameters
double a;
double b;
double c;
};
// return true if P is inside the triangle ABC

bool insideHull(const Point& p)
{
// choose anti-clockwise order of points such that points on the inside have a non-negative determinant
Line bottom (A, B);
Line topRight(B, C);
Line topLeft (C, A);
// all determinants must have a positive sign (or zero)
if (bottom .determinant(p) < -Epsilon) return false;
if (topRight.determinant(p) < -Epsilon) return false;
if (topLeft .determinant(p) < -Epsilon) return false;
// yeah ... found a "good" one :-)
return true;
}
// return true if the three lines a,b,c create a valid triangle inside the outermost triangle

bool isValidTriangle(const Line& a, const Line& b, const Line& c)
{
// find intersections of these three lines
Point ab = a.intersect(b);
Point bc = b.intersect(c);
Point ac = a.intersect(c);
// they must not be parallel
if (ab == NoIntersection) // note: this case was actually already tested in main()
return false;
if (bc == NoIntersection)
return false;
if (ac == NoIntersection)
return false;
// degenerated case: all lines have the same intersection point
if (ab == bc) // note: no need to test ab == ac and ac == bc
return false;
// intersections points must be inside the outer-most triangle
return insideHull(ab) && insideHull(bc) && insideHull(ac);
// insideHull(ab) was already tested in main()
}
int main()
{
// number of building blocks
unsigned int size = 36;
std::cin >> size;
// middle between A,B and A,C and B,C (of the basic triangle with size=1)
Point AB((A.x+B.x)/2, (A.y+B.y)/2);
Point AC((A.x+C.x)/2, (A.y+C.y)/2);
Point BC((B.x+C.x)/2, (B.y+C.y)/2);
// create all lines
std::vector<Line> lines;
// A-B
for (unsigned int i = 0; i < size; i++)
lines.push_back(Line(Point(A.x, i * Height), Point(B.x, i * Height)));
// A-BC
for (unsigned int i = 0; i < size; i++)
{
lines.push_back(Line(Point(i, A.y), Point(BC.x + i, BC.y)));
if (i > 0)
lines.push_back(Line(Point(-(double)i, A.y), Point(BC.x - (double)i, BC.y)));
}
// A-C
for (unsigned int i = 0; i < size; i++)
lines.push_back(Line(Point(i, A.y), Point(C.x + i, C.y)));
// B-C
for (unsigned int i = 0; i < size; i++)
lines.push_back(Line(Point(i+1, B.y), Point(C.x + i, C.y)));
// B-AC
for (unsigned int i = 0; i < 2*size-1; i++)
lines.push_back(Line(Point(i+1, B.y), Point(AC.x + i, AC.y)));
// C-AB
for (unsigned int i = 1; i < 2*size; i++)
lines.push_back(Line(Point(i * C.x, 0), Point(i * C.x, Height)));
// resize hull according to user input
A.x *= size; A.y *= size;
B.x *= size; B.y *= size;
C.x *= size; C.y *= size;
unsigned int count = 0;
// generate each combination of lines
for (unsigned int i = 0; i < lines.size(); i++)
for (unsigned int j = i + 1; j < lines.size(); j++)
{
// fast check whether the intersection of two lines results in a valid point
auto first = lines[i].intersect(lines[j]);
if (first == NoIntersection || !insideHull(first))
continue;
// and the third line ...
for (unsigned int k = j + 1; k < lines.size(); k++)
// now we have a potential triangle, check whether it's valid and fully inside the hull
if (isValidTriangle(lines[i], lines[j], lines[k]))
count++;
}
// display result
std::cout << count << std::endl;
return 0;
}

This solution contains 25 empty lines, 41 comments and 3 preprocessor commands.

# Interactive test

You can submit your own input to my program and it will be instantly processed at my server:

This is equivalent to`echo 2 | ./163`

Output:

*Note:* the original problem's input `36`

__cannot__ be entered

because just copying results is a soft skill reserved for idiots.

*(this interactive test is still under development, computations will be aborted after one second)*

# Benchmark

The correct solution to the original Project Euler problem was found in 0.05 seconds on a Intel® Core™ i7-2600K CPU @ 3.40GHz.

(compiled for x86_64 / Linux, GCC flags: `-O3 -march=native -fno-exceptions -fno-rtti -std=c++11 -DORIGINAL`

)

See here for a comparison of all solutions.

**Note:** interactive tests run on a weaker (=slower) computer. Some interactive tests are compiled without `-DORIGINAL`

.

# Changelog

July 7, 2017 submitted solution

July 7, 2017 added comments

# Hackerrank

see https://www.hackerrank.com/contests/projecteuler/challenges/euler163

My code solves **12** out of **12** test cases (score: **100%**)

# Difficulty

Project Euler ranks this problem at **70%** (out of 100%).

Hackerrank describes this problem as **easy**.

*Note:*

Hackerrank has strict execution time limits (typically 2 seconds for C++ code) and often a much wider input range than the original problem.

In my opinion, Hackerrank's modified problems are usually a lot harder to solve. As a rule thumb: brute-force is rarely an option.

# Links

projecteuler.net/thread=163 - **the** best forum on the subject (*note:* you have to submit the correct solution first)

# Heatmap

green problems solve the original Project Euler problem and have a perfect score of 100% at Hackerrank, too.

yellow problems score less than 100% at Hackerrank (but still solve the original problem).

gray problems are already solved but I haven't published my solution yet.

blue problems are solved and there wasn't a Hackerrank version of it at the time I solved it or I didn't care about it because it differed too much.

red problems are solved but exceed the time limit of one minute or the memory limit of 256 MByte.

*Please click on a problem's number to open my solution to that problem:*

1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |

51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |

76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |

101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |

126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 |

151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 |

176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 |

201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 |

226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 |

251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 |

276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 |

301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 |

326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 |

351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 |

376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 |

401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 |

426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 |

451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 |

476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 |

501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 |

526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 |

I scored 13,183 points (out of 15300 possible points, top rank was 17 out of ≈60000 in August 2017) at Hackerrank's Project Euler+.

Look at my progress and performance pages to get more details.

My username at Project Euler is

**stephanbrumme**while it's stbrumme at Hackerrank.

# Copyright

I hope you enjoy my code and learn something - or give me feedback how I can improve my solutions.

All of my solutions can be used for any purpose and I am in no way liable for any damages caused.

You can even remove my name and claim it's yours. But then you shall burn in hell.

The problems and most of the problems' images were created by Project Euler.

Thanks for all their endless effort.

<< problem 162 - Hexadecimal numbers | Numbers for which no three consecutive digits ... - problem 164 >> |