Divide and conquer on four quadrants
· algorithms
Split level N into four quadrants. Each one is the previous city after a map. The turns are the usual rotation matrix at ±π/2.
AcWing 98, Fractal City. A city of level . Blocks numbered along a road. I want the straight-line distance between the centers of blocks and . Each cell is a block; the numbers are the ids along the road.
N = 1:
1 ---- 2 | 4 ---- 3
N = 2 (four N=1 copies planted around, the road joining them end to end):
1 4 -- 5 -- 6 | | | 2 -- 3 8 -- 7 | 15 -- 14 9 -- 10 | | | 16 13 -- 12 -- 11
Same picture as a grid of ids:
1 4 5 6 2 3 8 7 15 14 9 10 16 13 12 11
N = 3 (four N=2 copies, same planting):
1 -- 2 15 -- 16 -- 17 20 -- 21 -- 22 | | | | | 4 -- 3 14 -- 13 18 -- 19 24 -- 23 | | | 5 8 -- 9 12 31 -- 30 25 -- 26 | | | | | | | 6 -- 7 10 -- 11 32 29 -- 28 -- 27 | 59 -- 58 55 -- 54 33 36 -- 37 -- 38 | | | | | | | 60 57 -- 56 53 34 -- 35 40 -- 39 | | | 61 -- 62 51 -- 52 47 -- 46 41 -- 42 | | | | | 64 -- 63 50 -- 49 -- 48 45 -- 44 -- 43
Same picture as a grid of ids:
1 2 15 16 17 20 21 22 4 3 14 13 18 19 24 23 5 8 9 12 31 30 25 26 6 7 10 11 32 29 28 27 59 58 55 54 33 36 37 38 60 57 56 53 34 35 40 39 61 62 51 52 47 46 41 42 64 63 50 49 48 45 44 43goes up to . There are blocks. I am not drawing that.
Two points
The distance is Euclidean, then rounded to an integer. If I have the two centers, I am done. The whole job is: given a level and a block id, find the center. Origin at the city center, right, up.
Level I can write down:
Split, then ask what changed
There is a divide-and-conquer move here. Cut the level- city into four quadrants. Each quadrant has
blocks — a whole city of level . The first ids live northwest, the next northeast, then southeast, then southwest, the same order the road already used at level .
So I recurse: which quadrant, then a smaller city. The remaining job is to look at that copy against the previous city and write down the map that was applied — numbering, rotation, shift.
A copy of level has side blocks. Its own center sits halfway from the parent center to the parent corner, so the shift is
The rotation matrix
A rotation of the plane by (counterclockwise positive) is
Plug in : , , and
That is clockwise.
Plug in : , , and
That is counterclockwise.
The east copies keep the previous city as it was: only a shift. The west copies turn.
Four maps
Look at the northwest of in the picture: down to across to up to . The road walks this copy backwards versus level , and the square has been turned clockwise. Same reverse numbering in the southwest, turned the other way.
Northwest, . Local id maps to . Rotate by , shift by :
Northeast, . Same orientation, shift by :
Southeast, . Same orientation, shift by :
Southwest, . Local id , then the same reverse . Rotate by , shift by :
Check block at level . Then , , northwest, . Level block is , and
Block : southwest, , . Level block is , and
Distance . That is the sample.
I only ever recurse on the level, so one query is .
const int kBlockSideMeters = 10;
struct BlockCenter { long long x; long long y;};
[[nodiscard]] BlockCenter find_block_center(int city_level, long long block_id) { if (city_level == 1) { if (block_id == 1) { return BlockCenter{.x = -kBlockSideMeters / 2, .y = kBlockSideMeters / 2}; }
if (block_id == 2) { return BlockCenter{.x = kBlockSideMeters / 2, .y = kBlockSideMeters / 2}; }
if (block_id == 3) { return BlockCenter{.x = kBlockSideMeters / 2, .y = -kBlockSideMeters / 2}; }
return BlockCenter{.x = -kBlockSideMeters / 2, .y = -kBlockSideMeters / 2}; }
// 4^(city_level - 1) = 2^(2*(city_level-1)) long long blocks_per_quadrant = 1LL << (2 * (city_level - 1));
// Offset from city center to a quadrant center: 2^(city_level-2) blocks * // 10m. long long quadrant_offset = (1LL << (city_level - 2)) * kBlockSideMeters;
if (block_id <= blocks_per_quadrant) { // First quadrant: the road walks this copy backwards vs. the previous // level (see the N=1 / N=2 diagram). Local id i maps to // blocks_per_quadrant + 1 - i. long long subcity_block_id = blocks_per_quadrant - block_id + 1; auto subcity_center = find_block_center(city_level - 1, subcity_block_id);
return BlockCenter{.x = subcity_center.y - quadrant_offset, .y = -subcity_center.x + quadrant_offset}; }
if (block_id <= 2 * blocks_per_quadrant) { long long subcity_block_id = block_id - blocks_per_quadrant; auto subcity_center = find_block_center(city_level - 1, subcity_block_id);
return BlockCenter{.x = subcity_center.x + quadrant_offset, .y = subcity_center.y + quadrant_offset}; }
if (block_id <= 3 * blocks_per_quadrant) { long long subcity_block_id = block_id - (2 * blocks_per_quadrant); auto subcity_center = find_block_center(city_level - 1, subcity_block_id);
return BlockCenter{.x = subcity_center.x + quadrant_offset, .y = subcity_center.y - quadrant_offset}; }
// Fourth quadrant: same reverse numbering as the first quadrant. long long local_id = block_id - (3 * blocks_per_quadrant); long long subcity_block_id = blocks_per_quadrant + 1 - local_id; auto subcity_center = find_block_center(city_level - 1, subcity_block_id);
return BlockCenter{.x = -subcity_center.y - quadrant_offset, .y = subcity_center.x - quadrant_offset};}
[[nodiscard]] int distance_between_blocks(int city_level, long long block_id_a, long long block_id_b) { auto center_a = find_block_center(city_level, block_id_a); auto center_b = find_block_center(city_level, block_id_b); double delta_x = static_cast<double>(center_a.x - center_b.x); double delta_y = static_cast<double>(center_a.y - center_b.y); return static_cast<int>(std::lround(std::hypot(delta_x, delta_y)));}