<< 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")
Point
) on the line into the line equation ax + by = c, wherea = 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
).
Interactive test
You can submit your own input to my program and it will be instantly processed at my server:
This is equivalent toecho 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)
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.
Benchmark
The correct solution to the original Project Euler problem was found in 0.05 seconds on an Intel® Core™ i7-2600K CPU @ 3.40GHz.
(compiled for x86_64 / Linux, GCC flags: -O3 -march=native -fno-exceptions -fno-rtti -std=gnu++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
Please click on a problem's number to open my solution to that problem:
green | solutions solve the original Project Euler problem and have a perfect score of 100% at Hackerrank, too | |
yellow | solutions score less than 100% at Hackerrank (but still solve the original problem easily) | |
gray | problems are already solved but I haven't published my solution yet | |
blue | solutions are relevant for Project Euler only: there wasn't a Hackerrank version of it (at the time I solved it) or it differed too much | |
orange | problems are solved but exceed the time limit of one minute or the memory limit of 256 MByte | |
red | problems are not solved yet but I wrote a simulation to approximate the result or verified at least the given example - usually I sketched a few ideas, too | |
black | problems are solved but access to the solution is blocked for a few days until a new problem is published | |
the flashing problem is the one I solved most recently |
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 |
551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 |
576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 |
601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 |
I scored 13,486 points (out of 15700 possible points, top rank was 17 out of ≈60000 in August 2017) at Hackerrank's Project Euler+.
My username at Project Euler is stephanbrumme while it's stbrumme at Hackerrank.
Look at my progress and performance pages to get more details.
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 >> |