--- /dev/null
+#!/usr/bin/perl -w
+
+use v5.36;
+use Data::Dumper;
+use Array::Heap;
+use feature 'multidimensional';
+
+my @map = map { chomp; [ split // ] } <>;
+
+my %pos_of;
+for my $y (0 .. $#map) {
+ for my $x (0 .. $#{ $map[0]}) {
+ my $c = $map[$y][$x];
+ next if $c !~ /[a-z@]/;
+ $pos_of{$c} = [ $x, $y ];
+ }
+}
+
+my $nkeys = keys %pos_of;
+say "nkeys = $nkeys";
+
+$; = ';';
+
+my $best;
+
+my $min_dist = 1_000_000;
+
+my %min_cost;
+my @q0;
+sub walk {
+ my ($cost, @opened) = @_;
+
+ my %opened = map { $_ => 1 } @opened;
+
+ my $key = $opened[0] . join('', sort @opened);
+
+ if (defined $min_cost{$key} && $min_cost{$key} <= $cost) {
+ return;
+ }
+
+ $min_cost{$key} = $cost;
+
+ say "walk $min_dist $cost - $key ", length $key;
+ if ($nkeys + 1 == length $key) {
+ say "Found!";
+ exit 1;
+ }
+
+ my $p = $pos_of{$opened[0]};
+ my @q = [ @$p, $cost ];
+
+ my %seen;
+ $seen{$p->[0],$p->[1]} = 1;
+
+ my %dist;
+
+ while (my $pos = shift @q) {
+ my ($x, $y, $steps) = @$pos;
+
+ my $v = $map[$y][$x];
+ next if $v eq '#';
+ if ($v =~ /[a-z]/ && !$opened{$v}) {
+ $dist{$v} //= $steps;
+ # say "opened=". scalar @opened;
+ $min_dist = $steps if $min_dist > $steps
+ && @opened == keys %pos_of;
+ push_heap @q0, [ $steps, $v, @opened ];
+ }
+ if ($v =~ /[A-Z]/) {
+ next if !$opened{lc($v)};
+ }
+
+ for my ($dx, $dy) (1, 0, 0, 1, -1, 0, 0, -1) {
+ my ($nx, $ny) = ($x + $dx, $y + $dy);
+ next if $seen{$nx,$ny}++;
+ push @q, [ $nx, $ny, $steps+1 ]
+ if $steps + 1 < $min_dist;
+ }
+ }
+}
+
+push_heap @q0, [0, '@'];
+while (@q0) {
+ my $elem = pop_heap @q0;
+ walk(@$elem);
+}
+
+say $min_dist;
--- /dev/null
+#!/usr/bin/perl -w
+
+use v5.36;
+use Data::Dumper;
+use Array::Heap;
+use feature 'multidimensional';
+
+my @map = map { chomp; [ split // ] } <>;
+
+my %pos_of;
+for my $y (0 .. $#map) {
+ for my $x (0 .. $#{ $map[0]}) {
+ my $c = $map[$y][$x];
+ next if $c !~ /[a-z1-4]/;
+ $pos_of{$c} = [ $x, $y ];
+ }
+}
+
+my $nkeys = keys %pos_of;
+say "nkeys = $nkeys";
+
+$; = ';';
+
+my $best;
+
+my $min_dist = 1_000_000;
+
+my %min_cost;
+my $max_opened;
+my $max_cost;
+my @q0;
+sub walk {
+ my ($cost0, $cost, @opened) = @_;
+
+ my @robots = splice(@opened, 0, 4);
+ my %opened = map { $_ => 1 } @opened;
+
+ my $key = join('', $robots[0], sort(@robots[1..3]), '|', sort @opened);
+
+ if (defined $min_cost{$key} && $min_cost{$key} <= $cost) {
+ return;
+ }
+
+ $min_cost{$key} = $cost;
+
+ $max_opened = @opened if (!defined $max_opened || @opened > $max_opened);
+ if (!defined $max_cost || $max_cost < $cost) {
+ $max_cost = $cost;
+ say "walk $cost - $key ", length $key, ' ', $max_opened;
+ }
+ if ($nkeys == @opened) {
+ say "Found!";
+ exit 1;
+ }
+
+
+ my $p = $pos_of{$robots[0]};
+ my @q = [ @$p, $cost ];
+
+ my %seen;
+ $seen{$p->[0],$p->[1]} = 1;
+
+ my %dist;
+
+ while (my $pos = shift @q) {
+ my ($x, $y, $steps) = @$pos;
+
+ my $v = $map[$y][$x];
+ next if $v eq '#';
+ if ($v =~ /[a-z]/ && !$opened{$v}) {
+ $dist{$v} //= $steps;
+ my $cost = $steps + $nkeys - @opened;
+ # say "opened=". scalar @opened;
+ push_heap @q0, [ $cost, $steps, $v, @robots[1..3], $v, @opened ];
+ push_heap @q0, [ $cost, $steps, $robots[1], $v, @robots[2..3], $v, @opened ];
+ push_heap @q0, [ $cost, $steps, $robots[2], $v, @robots[1,3], $v, @opened ];
+ push_heap @q0, [ $cost, $steps, $robots[3], $v, @robots[1..2], $v, @opened ];
+ }
+ if ($v =~ /[A-Z]/) {
+ next if !$opened{lc($v)};
+ }
+
+ for my ($dx, $dy) (1, 0, 0, 1, -1, 0, 0, -1) {
+ my ($nx, $ny) = ($x + $dx, $y + $dy);
+ next if $seen{$nx,$ny}++;
+ push @q, [ $nx, $ny, $steps+1 ];
+ }
+ }
+}
+
+push_heap @q0, [0, 0, 1, 2, 3, 4, 1, 2, 3, 4];
+while (@q0) {
+ my $elem = pop_heap @q0;
+ walk(@$elem);
+}
+