Hey admins, I've sent a couple of tests to timus_support@acm.timus.ru that my AC solution was struggling with. Please validate if they can be added to the system. Your tests have been added. Thanks! Milanin, thanks a lot for new tests! Now I've got AC only with neural networks solution. All solutions with just "optimizations"/"bad subfields & patterns", etc. didn't allow me to pass new tests. This is impressive. I still can't believe that there's a problem on Timus that has a meaningful neural network based solution. Finally, 0.062 sec without neural networks. Just correct removing of "bad" fields, effective hash/pq implementations + bit tricks & memory optimizations + A*. I have been working on this problem since it appeared - the 2007 student quarterfinal, I went there with a team from SUrSU. Later I tried many different approaches and in practice, the hardest tests in this problem are those with 12 and 13 boxes. In the end, the solution turned out as follows: 1. Use uint64_t bit masks to store the board state (36 bits for the box positions + the coordinates of the upper-left corner of the connected component in which Stas is located) + 22 bits remain for additional information. A transition between vertices of the position graph is performed specifically by PUSHING a box, not by moving Stas. 2. Traverse the position graph using BFS with A* according to the g+h principle, where g is the exact cost of the path from the starting point to the current vertex, and h is a heuristic estimate from the current vertex to the final state. 3. Check for repeated visits to vertices of the graph using a hash table (implemented independently, with no STL containers, as they reduce performance and increase memory usage). 4. Store all examined boards in a fixed-maximum-size priority queue, which I implemented as a heap with K children at each node (K does not necessarily equal 2; it turns out that for this problem K = 4 or 6 is more advantageous). 5. To quickly find all cells of the connected component reachable by Stas from his current position without pushing boxes, you should use not bfs with a queue, but a wave algorithm on bit masks. const uint64_t M36 = (1ULL<<36)-1; Free cells: opened = ~(walls | boxes) & M36; Starting from the player's start bit, we expand the region through neighboring cells until a fixed point is reached. r = start; do { old = r; r |= neighbors(r) & open; } while (r != old); 6. Generation of all possible box pushes from the current position. Instead of a "each box X four directions" loop, for each direction one 36-bit mask of boxes that can be pushed is constructed. In the general case, the condition consists of three factors: the cell contains a box + behind the box there is a cell reachable by the player + in front there is a free cell. These sets are combined through AND after the required shifts. Then only the set bits are iterated over: while (z) { int q = ctz(z); z &= z-1; ... } This is especially efficient because the board is small, while most boxes cannot be pushed in a given direction. 7. Before the main search, immediately after reading the board, for each goal cell a BFS for one box is built in the reverse direction. The other boxes are ignored, but walls and push geometry are taken into account: for a box to be pushed from the previous cell into the current one, both a cell for the box itself and a support cell for the player are needed. We obtain pullDist[goal][cell], as well as pullMin[cell] - the minimum number of pushes to any goal, mask_r[cell] - a bit mask of goals that a box from this cell is capable of reaching at all. If mask_r[cell] == 0 and the cell is not a goal, a new box in it is statically dead. This is much stronger than a simple corner check. 8. Fast heuristic update: on a push, exactly one box q->d moves. Therefore h is not recalculated over all boxes, h_new = h_old - cost[q] + cost[d]. After push q->d, it is not always necessary to perform a full flood fill to determine Stas's component. The old region R changes as follows: d becomes occupied, q becomes free, and the player starts from q. If d did not belong to R or had no more than one neighbor from R, removing d cannot split the component into two parts. Then R can be corrected locally and expanded only through the newly freed q. 9. Before adding a new position to the priority queue, of course, it is worth checking it for unsolvability. The key point of this problem is that you should not be afraid of complex and time-consuming methods for pruning bad positions, the main thing is to prune as many of them as possible. Even solutions that took every 4 by 4 square on the board, fed it into a trained neural network with 2 hidden layers of 60 neurons each, and asked it whether this 4 by 4 combination was unsolvable or not passed within the time limit for me, although it would seem that this already involves 9 * 3600 float multiplication operations (at least), and doing this for every board would take a very long time, but NO, IT WILL NOT, if FEW of those boards remain. So, this is how I pruned bad positions: (!!!) Important note (!!!) We perform the unsolvability check specifically taking into account the position into which the moved box has entered. There is no need to check the lower corner 2 by 2 square on a 6 by 6 board if we moved a box in the top row. This greatly speeds up the checks.
a) the perimeter of the board (the simplest case - the number of boxes along the perimeter and the number of goal cells along the perimeter; more complex - along each side taking attached walls into account, a set of segments along the perimeter, each with its own mask-based check) b) all combinations of occupied 2 by 2 cells (with wall/box variations) - if it is unsolvable, then entire field is unsolvable (this gives 4 squares to check on a 6 by 6 board, since we will check only those adjacent to the moved box) c) all variants of 3 by 3 combinations that no longer contain 2 by 2 blockages, since they were checked at the previous step, which means that it is enough to check 3 by 3 combinations with an empty central cell, which, of course, may also be a goal (we check 8 such squares, depending on which cell of that square corresponds to the moved box) d) there are very many 4 by 4 variants (you must also remember to take into account that Stas may be either inside the selected 4 by 4 sub-square or outside it, so if the combination is encoded with masks, it takes 49 bits: 16 bits each for the positions of boxes and goals inside the 4 by 4, 16 possible positions of Stas inside and 1 outside, 3*16+1 bits). I implemented a generator of unsolvable 4 by 4 squares taking into account that all cells outside it are made goal cells, since we do not know the context of the board when extracting a 4 by 4 square from it, so goals there could be anywhere, and solvability can also depend on that. Of course, during generation, I did not include in the collection those positions that were already pruned by my perimeter checks and 2 by 2 and 3 by 3 squares, only NEW blockage variants. Generation took a couple of months. This produced about 70 million combinations for the corner placement of the 4 by 4 sub-square and slightly fewer for the placement where the upper corner is in cell (0,1) of the original 6 by 6 board. Of course, such a number of combinations cannot be inserted into the source code as a constant array. I tried to train a neural network, but first of all, it takes a long time to query it for an answer (see above, many float multiplications; networks smaller than 120 neurons did not train at all), and second, it still gives a probabilistic answer and on some Timus tests (especially newly added ones) it may classify a solvable combination (even if only one out of 10000) as unsolvable, and it turns out that pruning such a board just once or moving it to the end of the priority queue is enough to fail to find a solution in time. Therefore, although I managed to fit the solution with the neural network into 4.5 seconds, I discarded this variant as inefficient. An analysis of the resulting collection and identification of common patterns that can be checked simply with bit masks worked much better here; of course, these common patterns did not cover the entire range of bad boards, but I managed to prune about 80 % of the collection. I no longer generated or considered the 5 by 5 and 6 by 6 variants - it is pointless, there are too many combinations. e) Another class of deadlocks is a small rectangular pocket bounded by walls/boxes, with too few goals inside. The player must not be inside the rectangle being considered + internal goals are taken into account + only small sizes for which the checking rule is provable and safe are considered. 10. The order (!) of the board solvability checks and the check for whether the board is in the hash table is important. It is better to first perform the simple checks (up to 2 by 2), then check for presence in the hash, and then perform the complex checks. 11. Not only the box cell is important, but also the side from which the player must approach it. For each (cell,side) pair, msk[cell][side] is calculated - a mask of goals reachable by one box on the static board with the corresponding orientation of the player's approach to the box. From this, a mask of other boxes is also constructed which, together with the new box, create a two-box conflict over the set of reachable goals. 12. An important optimization was that local 2x2,3x3,4x4 windows do not see interactions between boxes located at a distance from one another. For difficult tests, certain four boxes are also selected from all boxes, the remaining boxes are temporarily removed, and if even after removing the other boxes the selected four cannot reach four goals, the full state is unsolvable. Four distinct cells are sorted, the number of combinations is C(36,4)=58905, but solvability also depends on the side/from which component the player can act. Therefore, I store a 36-bit mask of player cells from which this four-box case is solvable in the simplified problem. 13. I had to optimize the hash, making it initially small at 2^16, then increasing it if the combinations grew substantially. I also used _mm_loadu_si128 / _mm_cmpeq_epi16 for hash comparison. It was also important to avoid any dynamically allocated memory: no new/delete, no STL containers. 14. I encountered the problem of insufficient source-code size (64 KB is still too little for large solutions, there is not much room, especially if storing rules for pruning 4 by 4 boards and so on). All such additional static information had to be encoded in a large constant string using alphabetic characters/digits and so on, and decoded during program execution. Variable names had to be kept short, which makes the code difficult to understand, even line breaks sometimes had to be removed to fit into 64 KB. I asked above on the forum to increase the size, this seems like it should not be difficult, but no one heard me. Edited by author 04.09.2026 19:09 Edited by author 04.09.2026 19:20 New tests have been added. All accepted solutions have been rejudged. 15 out of 20 authors lost the Solved status for this problem. Finding out a testcase to improve algorithm does not mean that all submissions use information about tests. I have many submissions that AC, but the code is not based on specific test cases. At least adding strong test and rejudge is OK, removing totally - no. Edited by author 28.01.2026 09:30 My max value is 13: ######## #@ $.$.# # $.$.# #$ $.$.# #. $.$.# #$ $.$.# #. $.# ######## Has anybody found more? 33 boxes: ######## #******# #******# #******# #******# #******# #***.$@# ######## Edited by author 16.09.2026 16:13 Some hints for solving this problem the way I was able to solve it, without greedy search 1. I used BFS search with heuristic (A* search). As a heuristic, I used the sum of the distances from the boxes to the nearest goals, taking into account that different boxes should be on different goals. It is better to distribute boxes among goals in some greedy way, so as not to spend a lot of time on this. 2. In the board states, DO NOT store the position of the player, store the places he can REACH. This will greatly reduce the number of states needed to be stored. You can store one board state in two 64-bit numbers as bit masks. 3. To find bad positions and stop further search on them, look for simple deadlocks (the box is in a corner and not on a target), dynamic deadlocks (the boxes block each other) and NOT COMPLEX corral deadlocks (the boxes are not on goals and block access to some board area, so they cannot be pushed out of there). 4. If you try to search for too complex corral deadlocks, the time spent on finding them will not be worth it. Try different settings of what difficulty of corral deadlocks to search for and when to stop the search. Store the found configurations of corral deadlocks (and the configurations in which no deadlock was found) in some sets so as not to determine them every time. 5. In addition to the forward search (pushing boxes from the starting positions to the goals), you can also use the backward search (pulling boxes from the goals to the starting positions) to reduce the depth of the search tree. 6. For the forward (backward) search, prevent situations when, for example, there are more (fewer) boxes near the wall than goals. This can greatly speed up the search for some boards. 7. To understand why your algorithm is working too long for a particular board, you can find and output long deadlock branches - a sequence of pushes/pulls in the search tree that starts from one of the board states that is in the found solution and that does not eventually lead to the solution. This way you can find bugs when the algorithm did not find one of the types of deadlocks and did not stop searching on them. Good luck! Can somebody give me some hard test - my program works fine on all tests, which I can think out. BTW, here is some of them: ######## #.OOOOO# #$O$O$O# #OO$O$O# #@O$O$O# ###***O# #......# ######## ######## #.O$OO.# #O$..$O# #$$..$O# #O$..$$# #@$O.$O# #..$OO.# ######## ######## #.O$OO.# #O$..$O# #$$..$O# #O$..$$# #@$..$O# #.O$OO.# ######## ######## #......# ###***O# #@O$O$O# #OO$O$O# #$O$O$O# #.OOOOO# ######## ######## #.....O# #$$$$$O# #OO...O# #$$$$$O# #O.OO$.# #.O$.$+# ######## ######## #......# ###***O# #@O$O$O# #OO$O$O# #OO$O$O# #OOOOOO# ######## ######## #.$.$OO# #@OOOOO# #O$$$$$# #O.....# #$$$$$$# #......# ######## ######## #.OOO.O# #@$O##O# #OOO*OO# #.$O$O$# #$$$$..# #...OOO# ######## ######## #OOOO.O# #@OO##O# #OOO*OO# #.$O$OO# #$$$$.O# #...OOO# ######## ####### #@$OO.# #$O$OO# #.O.OO# ####### P.S. space I've changed to O. Edited by author 03.11.2009 20:42 AC finally. I have TLE 62 too. Don't have ideas of how to proceed. Can you give a hint of how you have improved you program to pass this test? I've stored only those positions, which can not be processed in some steps by greedy algo + store only positions, where man is in lowest-left cell + stop, when position is surely bad (I've used a lot of classes of bad positions) + make moves, that is surely necessary. Hi Oracle, can u give us some cases that considered as a bad position? I have a prunning issue also on my solution Hi guys, i've tried to solve this problem with A*. I came with pull distance as heuristic and calculate the distance table with Floyd's algorithm. I also have put deadlock detector (for Simple and Frozen) on my code. Is there something i miss? Or do you have any other suggestion? I am very happy if i can discuss with you guys. Thanks before for your help :) I know some interesting optimisations which helped me get TLE 78 but I don't understand how to use A* in this problem. Let's discuss it. My email is imoskovchenko72@gmail.com I think the answer is probablye Yes... I think it is relating to the number of inverse pairs of permuation.. Many thank to Oracle[Lviv NU]. His test data helped me a lot. But now my program solves all that tests as well as any test that I can imagine. Can anyone give test cases that he consider to be hard. Thanks! I've found one test that takes about 20 seconds to process. It looks quite simple but for my greedy approach it's real hell ######## #......# #------# #*****-# #------# #$$$$$$# #-----@# ######## Now it takes about 3 seconds to process this test but still TL 57-62... Other not bad test: ######## #.....-# #--##--# #-*----# #--#-#-# #$$$$$$# #-----+# ######## Edited by author 01.05.2011 00:06 Edited by author 01.05.2011 02:34 It looks hardly possible to pass this problem in Java. I have really good solution but depending on configuration I always get TL 56,57 or 62. Maybe I've missed cool truncation? I store situation in one long variable in bit representation. In this case it's easy to check if all blocks are on their places (just do binary &) Also I use 3 different heuristics to filter deadlock situations (Block with unreachable destination, stuck block not on destination, strongly connected area without man inside and with amount of blocks greater then amount of destinations) Also I use priority queue and process turns wich moves blocks closer to closest unblocked destination than it was in previous turn. I turn off heuristics automatically if they are not effective in current situation. (it looks effective on some tests but it doesn't actually help to pass 62 :D ) I'm pretty sure that even simpler C++ solution can pass all tests. Java, Java, why you are so slow?... Time limit is too strict. Admins, is it good idea to add this problem to list of problems that one can have problem with using Java? Guys... If you have good ideas of how to improve don't hesitate to write them here. Thanks, Vitaliy Edited by author 01.05.2011 02:31 My method needs a plenty of memory to store information, although got a Memory Limit Exceeds, but it can give out a solution to all the hard data in discuss in less than 0.1s. however, I still found some data that make my program run very slow(up to 2s), and even can not provide a solution(the solution exists). ######## #.O....# #####OO# #.@$OO$# #$O$O$O# #OO$O$O# #.$OOO.# ######## ######## #.O.O..# #####OO# #.@$OO$# #$O$O$O# #OO$O$O# #.$OO..# ######## ######## #......# #.OOO$@# #.O$OO$# #$O$O$O# #O$$O$O# #.$OOO.# ######## ######## #......# #.OOO$O# #.@$OO$# #$O$O$O# #O$$O$O# #.$OOO.# ######## can any one who passed this problem share some more outstanding ideas about how to optimize the algorithm? Many thank to Oracle[Lviv NU]. His test data helped me a lot. But now my program solves all that tests as well as any test that I can imagine. Can anyone give test cases that he consider to be hard. Thanks! ###### ## . # # * # # # .$ # # #$## ## @ # ##### my personal favorites: ######## ##.* $.# # * . # # *. # ## $.$ # # $*$$$# #.. @# ######## and next is hard only if you don't use any estimate func: ######## #. # #$ $ $ # #@ $ $ # # $ $ # # ****# #......# ######## Edited by author 17.08.2018 15:25 visual C++ 2017 can't use gets() too egg hurt I think this is a no solution test I output empty and pass this test... wa haha AC again,hahahaha two hardest problems if consider the position of player there are about 7.5*10^8(upper_bound),state, we can come up some idea to compact the states,and use a huge array instead of hash_table to prevent the duplication of states this may far speed up your program Edited by author 15.11.2016 13:02 Edited by author 15.11.2016 13:02 maybe for every 3*10^7 possible states,we can compact the postion of player and record the number of connected place,and use twice bfs search I think that will reduce the number of states we must store... 3 days overall. I'm your fan forever. Edited by author 03.05.2016 14:58 Can you give us some tips on your approach? Thanks in advance. Edited by author 03.05.2016 14:58 The problem is rejudged, one author have lost AC. Thanks to Erop [USU] Yeah, thanks to Erop [USU] :). I wish to add some tests too. I’ve sent them to "timus_support@acm.timus.ru". Did you receive them? If it is possible, can I see test #92 (or some similar)? I've spent a lot of time getting "Accepted", but still no idea, what kind of test is it. Thanks. P.S. my mail: Oracle@acm.lviv.ua Edited by author 05.09.2011 11:56 Edited by author 05.09.2011 11:56 What do you mean under "kind of test"? Walls, boxes... as usually. I think that it's very small to have a "kind". It has about 110 pushes in push-optimal solution. But, seems that pushes not a primary factor of hardiness. Here is the test (very easy) that has 100 pushes in push-optimal solution: ######## #._.*__# ##$_*__# ##__*_*# ##_*__.# #_$$**$# #@.____# ######## I suppose that it's hard because of huge amount of states passes through without pruning, and estimation function can't do anything in first 2/3 of search tree because of boxes are placed at goals and removed from them many times. Here is another test (hard. But, maybe, your solver cracks it instantly) with only 73 pushes: ######## #._.@__# #$$$*$*# #._$___# #.$.*_*# #_$.$__# #._.*__# ######## About kind: some boxes should be moved from one "room" to another, a lot of boxes on board, etc. Really, first test is quite easy (about 2 seconds for my algo on my laptop)... And second is real hell))) On my laptop it takes about 10 seconds to solve it. And solution is found only at the end of the search (all of about 800000 possible states should be tested). My solution do not give push-optimal solution nor move-optimal solution (and I do not use any estimation function at all), but nevertheless it is also hard for my algo too. Thanks a lot! I have a good test. Can you add it? Send it to our support email I've sent a test to "timus_support@acm.timus.ru". Have you received it? I have got new good test. Can you add it? Send it to our support email I've sent a test to "timus_support@acm.timus.ru". Hello, i am a newfag at this judge system, and i have some questions: The output information must be minimalize, or only no longer than 10000 symbols? Different and true solutions are all accepted? What biggest number 'n' could be? Edited by author 19.03.2010 03:26 IMHO just no longer than 10000 symbols. 3 ≤ n, m ≤ 8 Yes, any answer, not longer than 10000 symbols will be accepted. i got ac, but my program can't pass some tests.. can you add these tests? Edited by author 19.01.2008 01:06 Yes, send your tests to Sandro at Plotinka Ru and we'll add them. i've sent tests to your mail. have you received them? Can you, please, add my tests too? It seems to me, that they are quite hard. I've sent them to "timus_support@acm.timus.ru". Edited by author 07.11.2009 04:04 Hey, have somebody read my previsious post? Are admins alive? char em[10][20]; int n = 0, m = 0; ... while (gets(em[n++]) && em[n-1][0]); --n; m = (int) strlen(em[0]); ... this code doesn't pass test 3 and gets WA, but if i insert this line if (m != strlen(em[1])) while(true); it gets Time Limit Exceeded !!! check it Edited by author 16.01.2009 23:18 Lengths of lines may differ, because the spaces at the end of lines are omitted. I always TLE in 9# and 10#. How shuold I deal with it? |
|