<< problem 387 - Harshad Numbers | Sum of squares of divisors - problem 401 >> |
Problem 393: Migrating ants
(see projecteuler.net/problem=393)
An n * n grid of squares contains n^2 ants, one ant per square.
All ants decide to move simultaneously to an adjacent square (usually 4 possibilities, except for ants on the edge of the grid or at the corners).
We define f(n) to be the number of ways this can happen without any ants ending on the same square and without any two ants crossing the same edge between two squares.
You are given that f(4) = 88.
Find f(10).
My Algorithm
I solved this problem with Dynamic Programming:
- process the grid row-by-row, starting with the top row
- each row is assigned a state (consisting of three parts):
- row number
- a bit mask of all ants moving down from the row above
- a bit mask of all ants travelling up from the row below
There are 4^10 potential moves for a row of 10 ants.
Some of them are illegal: no ant can move left on the left-most square.
Vice versa, no ant may move right on the right-most square.
And on top of that, no ant may move right if its right neighbor moves left (ants' paths must not cross each other).
I spent quite some time finding out all possible illegal moves (you'll notice the widespread use of
invalid
in my code).A major performance speedup was achieved by skipping as many illegal sequences as possible (see
skipIds
).
Alternative Approaches
Some people found ways to solve this problem in a few milliseconds by looking for the cycles.
Interactive test
You can submit your own input to my program and it will be instantly processed at my server:
This is equivalent toecho 4 | ./393
Output:
Note: the original problem's input 10
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 <map>
// oeis.org/A216678
// at most 10x10 grid
const unsigned int MaxSize = 10;
// actual size
unsigned int size = 10;
typedef unsigned short Mask;
const Mask NoAnt = 0;
// memoization container
struct State
{
unsigned short row;
Mask down, up;
// create new state
State(unsigned int row_, Mask down_, Mask up_)
: row(row_), down(down_), up(up_)
{}
// for std::map
bool operator<(const State& other) const
{
if (row != other.row) return row < other.row;
if (down != other.down) return down < other.down;
return up < other.up;
}
};
std::map<State, unsigned long long> cache;
// encode each ant's move in 2 bits
enum Move
{
MoveUp = 0,
MoveRight = 1,
MoveDown = 2,
MoveLeft = 3
};
const unsigned int BitsPerMove = 2;
const unsigned int NumberOfMoves = 4;
const unsigned int MoveMask = 4 - 1;
// extract the 2 bit encoding a move in a bitmask
Move getMove(unsigned int bits, unsigned int pos)
{
auto mypos = size - (pos + 1);
return Move((bits >> (BitsPerMove * mypos)) & MoveMask);
}
// do the heavy lifting ...
unsigned long long search(unsigned int row = 0, Mask down = NoAnt, Mask up = NoAnt)
{
// "below" last row: no ant can exit the grid, therefore "up" and "up" must be empty
if (row == size)
{
const unsigned long long Symmetries = 2;
return (down == 0 && up == 0) ? Symmetries : 0;
}
// memoize
State state(row, down, up);
auto lookup = cache.find(state);
if (lookup != cache.end())
return lookup->second;
// count all valid movement patterns
unsigned long long result = 0;
// analyze each possible combination for the current row
const unsigned int combinations = 1 << (BitsPerMove * size); // 4^size
for (unsigned int i = 0; i < combinations; i++)
{
// can't go left on the left-most square
Move firstMove = getMove(i, 0);
if (firstMove == MoveLeft)
{
// skip all values of "i" until a different move appears
auto skipSquares = size - 1;
auto skipIds = 1 << (BitsPerMove * skipSquares);
i += skipIds - 1; // minus 1 because for-loop always increments by one
continue;
}
// can't go right on the right-most square
Move lastMove = getMove(i, size - 1);
if (lastMove == MoveRight)
continue;
// half of all grids start with an ant in the upper-left corner that is moving right
// => only analyze those and multiply the result by 2 (see the first lines of search(), there is "Symmetries = 2")
if (row == 0 && firstMove != MoveRight)
continue;
// look at each cell of the current row
Move previous = MoveRight;
bool invalid = false;
unsigned int failedAt = 0;
for (unsigned int pos = 0; pos < size; pos++)
{
auto current = getMove(i, pos);
auto bit = 1 << pos;
// can't cross an ant that moving down
invalid |= (current == MoveUp && (down & bit));
// upper row needs an ant to move up
invalid |= (current == MoveUp && !(up & bit));
invalid |= (current != MoveUp && (up & bit));
// ants can't cross each other's way
invalid |= (current == MoveLeft && previous == MoveRight);
// can't move down in last row
invalid |= (current == MoveDown && row + 1 == size);
if (invalid)
{
failedAt = pos;
break;
}
previous = current;
}
// restart with the next ID that starts with different moves
if (invalid)
{
// skip multiple numbers until teh square that failed is different
if (failedAt != size - 1)
{
auto skipSquares = size - (failedAt + 1);
auto skipIds = 1 << (BitsPerMove * skipSquares);
i += skipIds - 1; // minus 1 because for-loop always increments by one
}
continue;
}
// count total movement per cell: the final value has to be zero => one ant walks away (-1) and one ant arrives (+1)
int movement[MaxSize] = { 0 };
for (unsigned int pos = 0; pos < size; pos++)
{
auto bit = 1 << pos;
// an ant arrived from above
if (down & bit)
movement[pos]++;
// one ant walks away
movement[pos]--;
auto current = getMove(i, pos);
// to the left ?
if (current == MoveLeft)
movement[pos - 1]++;
// to the right ?
else if (current == MoveRight)
movement[pos + 1]++;
}
// prepare next row
Mask nextDown = NoAnt;
Mask nextUp = NoAnt;
for (unsigned int pos = 0; pos < size && !invalid; pos++)
{
auto bit = 1 << pos;
auto current = getMove(i, pos);
// an ant moves down :-)
if (current == MoveDown)
{
nextDown |= bit;
invalid |= (row + 1 == size);
}
// prevent too much traffic on a square
invalid |= (movement[pos] > 0 || movement[pos] < -1);
// need one ant to move up from the next row
if (movement[pos] == -1)
{
nextUp |= bit;
invalid |= (current == MoveDown);
}
}
if (invalid)
continue;
// go deeper
result += search(row + 1, nextDown, nextUp);
}
cache[state] = result;
return result;
}
int main()
{
std::cin >> size;
std::cout << search() << std::endl;
return 0;
}
This solution contains 31 empty lines, 35 comments and 3 preprocessor commands.
Benchmark
The correct solution to the original Project Euler problem was found in 2.8 seconds on an Intel® Core™ i7-2600K CPU @ 3.40GHz.
Peak memory usage was about 4 MByte.
(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
September 23, 2017 submitted solution
September 23, 2017 added comments
Difficulty
Project Euler ranks this problem at 50% (out of 100%).
Links
projecteuler.net/thread=393 - the best forum on the subject (note: you have to submit the correct solution first)
Code in various languages:
Python github.com/LaurentMazare/ProjectEuler/blob/master/e393.py (written by Laurent Mazare)
C++ github.com/roosephu/project-euler/blob/master/393.cpp (written by Yuping Luo)
Java github.com/thrap/project-euler/blob/master/src/Java/Problem393.java (written by Magnus Solheim Thrap)
Go github.com/frrad/project-euler/blob/master/golang/Problem393.go (written by Frederick Robinson)
Those links are just an unordered selection of source code I found with a semi-automatic search script on Google/Bing/GitHub/whatever.
You will probably stumble upon better solutions when searching on your own.
Maybe not all linked resources produce the correct result and/or exceed time/memory limits.
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 the next problem is published | |
[new] | the flashing problem is the one I solved most recently |
I stopped working on Project Euler problems around the time they released 617.
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 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 |
626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 |
I scored 13526 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 387 - Harshad Numbers | Sum of squares of divisors - problem 401 >> |