聘我网

新概念招聘3.0

Counterfeit Penny Problem(12硬币称3次)的程序解

vote up0vote downstar

问题描述

12个硬币,其中一个假的,给你一架天平,只准称三次,而且不知道假币比真币重还是轻

尝试了下,感觉比很多算法都有意思~

代码

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Storable qw(dclone);
use POSIX qw(floor);
use Getopt::Long;
use Getopt::Std;

sub counterfeit_coin_max {
    my ($x) = @_;
    return $x > 0 ? (3**$x-1)/2 : 1;            #(2)
}

sub counterfeit_coin_weight_xyz {
    my ($xy, $z, $G, $parent) = @_;
    my @nodes;
    if(not $parent->[4]){
        $parent->[4] = [];
    }
    if(in_array($xy->[@$xy - 1], $G)){
        pop @$xy;
    }
    #case 1: $xy balances
    push @nodes, [[@$z], [@$xy, @$G], [], []];
    push @{$parent->[4]}, {weight => $xy, node => $nodes[$#nodes]};
    my @part1 = @$xy[0..floor((@$xy - 1)/2)];
    my @part2 = @$xy[floor((@$xy + 1)/2)..@$xy - 1];

    #case 2: lt
    push @nodes, [[], [@$G, @$z], [@part2], [@part1]];
    push @{$parent->[4]}, {weight => $xy, node => $nodes[$#nodes]};
    #case 3: gt
    push @nodes, [[], [@$G, @$z], [@part1], [@part2]];
    push @{$parent->[4]}, {weight => $xy, node => $nodes[$#nodes]};
    return @nodes;
}

sub in_array {
    my ($x, $array) = @_;
    for my $e (@$array){
        return 1 if $e == $x;
    }
    return;
}

sub array_exclude {
    my ($a, $exc) = @_;
    my @result;
    map {push @result, $_ if not in_array($_, $exc)} @$a;
    return @result;
}


sub counterfeit_coin_update {
    my ($OH, $OL, $NH, $NL) = @_;
    my (@g, @h, @l);
    for my $e (@$OH, @$OL){
        if(in_array($e, $NH) and (not in_array($e, $OL))){
            push @h, $e;
        }
        elsif(in_array($e, $NL) and (not in_array($e, $OH))){
            push @l, $e;
        }
        else{
            push @g, $e;
        }
    }

    return (\@g, \@h, \@l);
}

sub counterfeit_coin_weight_hlg {
    my ($hlg, $H, $L, $G, $parent) = @_;
    my @nodes;
    if(not $parent->[4]){
        $parent->[4] = [];
    }
    if(in_array($hlg->[@$hlg - 1], $G)){
        pop @$hlg;
    }
    #case 1: balances
    push @nodes, [[], [@$G, @$hlg], [array_exclude($H, $hlg)], [array_exclude($L, $hlg)]];
    push @{$parent->[4]}, {weight => $hlg, node => $nodes[$#nodes]};
    my @part1 = @$hlg[0..floor((@$hlg -1)/2)];
    my @part2 = @$hlg[floor((@$hlg + 1)/2)..@$hlg - 1];

    my ($g, $h, $l);
    #case 2: lt
    ($g, $h, $l) = counterfeit_coin_update($H, $L, \@part2, \@part1);
    push @nodes, [
        [], 
        [@$G, @$g], 
        [@$h],
        [@$l]
    ];
    push @{$parent->[4]}, {weight => $hlg, node => $nodes[$#nodes]};
    #case 3: gt
    ($g, $h, $l) = counterfeit_coin_update($H, $L, \@part1, \@part2);
    push @nodes, [
        [], 
        [@$G, @$g], 
        [@$h],
        [@$l]
    ];
    push @{$parent->[4]}, {weight => $hlg, node => $nodes[$#nodes]};
    return @nodes;
}

=select $x from $H and $L bins 
=cut
sub counterfeit_coin_selecthl {
    my ($x, $H, $L, $G) = @_;
    if(not $x > 0){
        $x = 1;
    }
    my @hl;
    my ($more, $few);
    if(@$H >= @$L){
        $more = $H;
        $few = $L;
    }
    else{
        $more = $L;
        $few = $H;
    }
    if(exists $more->[$x - 1]){
        for my $i (0..$x - 1){
            push @hl, $more->[$i];
        }
    }
    else{
        # take all $more plus $nfew $few
        my $nfew = $x - @$more;
        my $half = floor(($x - 1)/2);
        # grab half $more and half $few, then the other half of both
        # so that left h + right l is close to left l + right h
        for my $i (0..floor((@$more - 1)/2)){
            push @hl, $more->[$i];
        }
        for my $i (0..$half - (floor(@$more + 1)/2)){
            push @hl, $few->[$i];
        }
        for my $i (floor((@$more - 1)/2) + 1..@$more - 1){
            push @hl, $more->[$i];
        }
        for my $i ($half - floor((@$more + 1)/2) + 1..$nfew - 1){
            push @hl, $few->[$i];
        }
    }
    if(1 == @hl%2){
        push @hl, $G->[0];
    }
    return @hl;
}

sub counterfeit_coin_branch {
    my ($k, @nodes) = @_;
    my @next;
    for my $node (@nodes){
        my ($E, $G, $H, $L) = @$node;
        if(@$E){
            if(1 == @$E){
                next;
            }
            #@z should contain no more than (3**(k-1)+1)/2            (1)
            #@xy should contain no more than 3**(k-1), plus it MUST be even
            my (@xy, @z);
            my $count = 0;
            my $xy = @$G ? 3**($k-1) : 3**($k-1) - 1;#(1) will hold according to (2)
            for my $i (@$E){
                if($count < $xy){
                    push @xy, $i;
                    $count++;
                }
                else{
                    push @z, $i;
                }
            }
            if(1 == @xy%2){
                push @xy, $G->[0];
            }
            #now that we've chosen the coins, let's do weighting
            push @next, counterfeit_coin_weight_xyz(\@xy, \@z, $G, $node);
        }
        elsif(@$H or @$L){
            if(1 == (@$H + @$L)){
                next;
            }
            #just ensure to leave a combined total of 3**(k-1) or less in the H and L bins
            my $hl = 3**($k-1);
            my @hl = counterfeit_coin_selecthl(@$H + @$L - $hl, $H, $L, $G);
            push @next, counterfeit_coin_weight_hlg(\@hl, $H, $L, $G, $node);
        }
    }
    return @next;
}

sub counterfeit_coin_print {
    my ($node, $indent) = @_;
    $indent ||= 0;
    my ($NE, $NG, $NH, $NL, $NC) = @$node;
    print ('|' . (' ' x $indent) . (join ',', @$NE) . '; ' . (join ',', @$NG) . '; ' . (join ',', @$NH) . '; ' . (join ',', @$NL) . '; ' . $/);
    if($NC and @$NC){
        print '|' . (' ' x ($indent + 2)) . '(weight ' . (join ',', @{$NC->[0]{weight}}) . ')' . $/;
        for my $child (@$NC){
            counterfeit_coin_print($child->{node}, $indent + 2);
        }
    }
}

sub counterfeit_coin_check {
    my ($x, $n, $root) = @_;
    my ($E, $G, $H, $L, $children) = @{$root};
    if(@$E + @$G + @$H + @$L != $n){
        print 'something wrong3 at ' . $x . $/;
        print Dumper($E);
        print Dumper($G);
        print Dumper($H);
        print Dumper($L);
        exit;
    }
    if($children){
        map {counterfeit_coin_check($x, $n, $_->{node});} @$children;
    }
}

sub counterfeit_coin {
    my ($n, $x) = @_;
    if($n > counterfeit_coin_max($x)){
        print "This is just impossible." . $/;
        return;
    }
    #initialize
    my (@E, @G, @H, @L, @next);
    @E = 1 .. $n;
    my $root = [\@E, \@G, \@H, \@L];
    push @next, $root;
    while($x > 0){
        @next = counterfeit_coin_branch($x, @next);
        counterfeit_coin_check($x, $n, $root);
        $x--;
    }
    counterfeit_coin_print($root);
}

my ($n, $x);
my $result = GetOptions("n=i" => \$n, "x=i" => \$x);
if(defined $n and defined $x)
{
    counterfeit_coin($n, $x);
}
else{
    my ($opt_n, $opt_x);
    getopts('n:x');
    if(defined $opt_n and defined $opt_x){
        $n = $opt_n;
        $x = $opt_x;
    }
    print $0 . " --n=n1 --x=n2" . $/;
    print $0 . " -n=n1 -x=n2" . $/;
}

试运行:

[zhiqiang.xu@dev-test test]$ perl counterfeit.pl -n=12 -x=3
|1,2,3,4,5,6,7,8,9,10,11,12; ; ; ;
|  (weight 1,2,3,4,5,6,7,8)
|  9,10,11,12; 1,2,3,4,5,6,7,8; ; ;
|    (weight 9,10,11)
|    12; 9,10,11,1,2,3,4,5,6,7,8; ; ;
|    ; 1,2,3,4,5,6,7,8,12; 11; 9,10;
|      (weight 9,10)
|      ; 1,2,3,4,5,6,7,8,12,9,10; 11; ;
|      ; 1,2,3,4,5,6,7,8,12,11,10; ; 9;
|      ; 1,2,3,4,5,6,7,8,12,11,9; ; 10;
|    ; 1,2,3,4,5,6,7,8,12; 9,10; 11;
|      (weight 9,10)
|      ; 1,2,3,4,5,6,7,8,12,9,10; ; 11;
|      ; 1,2,3,4,5,6,7,8,12,9,11; 10; ;
|      ; 1,2,3,4,5,6,7,8,12,10,11; 9; ;
|  ; 9,10,11,12; 5,6,7,8; 1,2,3,4;
|    (weight 5,6,1,7,8)
|    ; 9,10,11,12,5,6,1,7,8; ; 2,3,4;
|      (weight 2,3)
|      ; 9,10,11,12,5,6,1,7,8,2,3; ; 4;
|      ; 9,10,11,12,5,6,1,7,8,3,4; ; 2;
|      ; 9,10,11,12,5,6,1,7,8,2,4; ; 3;
|    ; 9,10,11,12,5,6,2,3,4; 7,8; 1;
|      (weight 7,8)
|      ; 9,10,11,12,5,6,2,3,4,7,8; ; 1;
|      ; 9,10,11,12,5,6,2,3,4,7,1; 8; ;
|      ; 9,10,11,12,5,6,2,3,4,8,1; 7; ;
|    ; 9,10,11,12,7,8,1,2,3,4; 5,6; ;
|      (weight 5)
|      ; 9,10,11,12,7,8,1,2,3,4,5; 6; ;
|      ; 9,10,11,12,7,8,1,2,3,4,5,6; ; ;
|      ; 9,10,11,12,7,8,1,2,3,4,6; 5; ;
|  ; 9,10,11,12; 1,2,3,4; 5,6,7,8;
|    (weight 1,2,5,3,4)
|    ; 9,10,11,12,1,2,5,3,4; ; 6,7,8;
|      (weight 6,7)
|      ; 9,10,11,12,1,2,5,3,4,6,7; ; 8;
|      ; 9,10,11,12,1,2,5,3,4,7,8; ; 6;
|      ; 9,10,11,12,1,2,5,3,4,6,8; ; 7;
|    ; 9,10,11,12,1,2,6,7,8; 3,4; 5;
|      (weight 3,4)
|      ; 9,10,11,12,1,2,6,7,8,3,4; ; 5;
|      ; 9,10,11,12,1,2,6,7,8,3,5; 4; ;
|      ; 9,10,11,12,1,2,6,7,8,4,5; 3; ;
|    ; 9,10,11,12,3,4,5,6,7,8; 1,2; ;
|      (weight 1)
|      ; 9,10,11,12,3,4,5,6,7,8,1; 2; ;
|      ; 9,10,11,12,3,4,5,6,7,8,1,2; ; ;
|      ; 9,10,11,12,3,4,5,6,7,8,2; 1; ;

Web CGI版本:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Storable qw(dclone);
use POSIX qw(floor);
use Getopt::Long;
use Getopt::Std;
use CGI qw/header/;

sub counterfeit_coin_max {
    my ($x) = @_;
    return $x > 0 ? (3**$x-1)/2 : 1;            #(2)
}

sub counterfeit_coin_weight_xyz {
    my ($xy, $z, $G, $parent) = @_;
    my @nodes;
    if(not $parent->[4]){
        $parent->[4] = [];
    }
    if(in_array($xy->[@$xy - 1], $G)){
        pop @$xy;
    }
    #case 1: $xy balances
    push @nodes, [[@$z], [@$xy, @$G], [], []];
    push @{$parent->[4]}, {weight => $xy, node => $nodes[$#nodes]};
    my @part1 = @$xy[0..floor((@$xy - 1)/2)];
    my @part2 = @$xy[floor((@$xy + 1)/2)..@$xy - 1];

    #case 2: lt
    push @nodes, [[], [@$G, @$z], [@part2], [@part1]];
    push @{$parent->[4]}, {weight => $xy, node => $nodes[$#nodes]};
    #case 3: gt
    push @nodes, [[], [@$G, @$z], [@part1], [@part2]];
    push @{$parent->[4]}, {weight => $xy, node => $nodes[$#nodes]};
    return @nodes;
}

sub in_array {
    my ($x, $array) = @_;
    for my $e (@$array){
        return 1 if $e == $x;
    }
    return;
}

sub array_exclude {
    my ($a, $exc) = @_;
    my @result;
    map {push @result, $_ if not in_array($_, $exc)} @$a;
    return @result;
}


sub counterfeit_coin_update {
    my ($OH, $OL, $NH, $NL) = @_;
    my (@g, @h, @l);
    for my $e (@$OH, @$OL){
        if(in_array($e, $NH) and (not in_array($e, $OL))){
            push @h, $e;
        }
        elsif(in_array($e, $NL) and (not in_array($e, $OH))){
            push @l, $e;
        }
        else{
            push @g, $e;
        }
    }

    return (\@g, \@h, \@l);
}

sub counterfeit_coin_weight_hlg {
    my ($hlg, $H, $L, $G, $parent) = @_;
    my @nodes;
    if(not $parent->[4]){
        $parent->[4] = [];
    }
    if(in_array($hlg->[@$hlg - 1], $G)){
        pop @$hlg;
    }
    #case 1: balances
    push @nodes, [[], [@$G, @$hlg], [array_exclude($H, $hlg)], [array_exclude($L, $hlg)]];
    push @{$parent->[4]}, {weight => $hlg, node => $nodes[$#nodes]};
    my @part1 = @$hlg[0..floor((@$hlg -1)/2)];
    my @part2 = @$hlg[floor((@$hlg + 1)/2)..@$hlg - 1];

    my ($g, $h, $l);
    #case 2: lt
    ($g, $h, $l) = counterfeit_coin_update($H, $L, \@part2, \@part1);
    push @nodes, [
        [], 
        [@$G, @$g], 
        [@$h],
        [@$l]
    ];
    push @{$parent->[4]}, {weight => $hlg, node => $nodes[$#nodes]};
    #case 3: gt
    ($g, $h, $l) = counterfeit_coin_update($H, $L, \@part1, \@part2);
    push @nodes, [
        [], 
        [@$G, @$g], 
        [@$h],
        [@$l]
    ];
    push @{$parent->[4]}, {weight => $hlg, node => $nodes[$#nodes]};
    return @nodes;
}

=select $x from $H and $L bins 
=cut
sub counterfeit_coin_selecthl {
    my ($x, $H, $L, $G) = @_;
    if(not $x > 0){
        $x = 1;
    }
    my @hl;
    my ($more, $few);
    if(@$H >= @$L){
        $more = $H;
        $few = $L;
    }
    else{
        $more = $L;
        $few = $H;
    }
    if(exists $more->[$x - 1]){
        for my $i (0..$x - 1){
            push @hl, $more->[$i];
        }
    }
    else{
        # take all $more plus $nfew $few
        my $nfew = $x - @$more;
        my $half = floor(($x - 1)/2);
        # grab half $more and half $few, then the other half of both
        # so that left h + right l is close to left l + right h
        for my $i (0..floor((@$more - 1)/2)){
            push @hl, $more->[$i];
        }
        for my $i (0..$half - (floor(@$more + 1)/2)){
            push @hl, $few->[$i];
        }
        for my $i (floor((@$more - 1)/2) + 1..@$more - 1){
            push @hl, $more->[$i];
        }
        for my $i ($half - floor((@$more + 1)/2) + 1..$nfew - 1){
            push @hl, $few->[$i];
        }
    }
    if(1 == @hl%2){
        push @hl, $G->[0];
    }
    return @hl;
}

sub counterfeit_coin_branch {
    my ($k, @nodes) = @_;
    my @next;
    for my $node (@nodes){
        my ($E, $G, $H, $L) = @$node;
        if(@$E){
            if(1 == @$E){
                next;
            }
            #@z should contain no more than (3**(k-1)+1)/2            (1)
            #@xy should contain no more than 3**(k-1), plus it MUST be even
            my (@xy, @z);
            my $count = 0;
            my $xy = @$G ? 3**($k-1) : 3**($k-1) - 1;#(1) will hold according to (2)
            for my $i (@$E){
                if($count < $xy){
                    push @xy, $i;
                    $count++;
                }
                else{
                    push @z, $i;
                }
            }
            if(1 == @xy%2){
                push @xy, $G->[0];
            }
            #now that we've chosen the coins, let's do weighting
            push @next, counterfeit_coin_weight_xyz(\@xy, \@z, $G, $node);
        }
        elsif(@$H or @$L){
            if(1 == (@$H + @$L)){
                next;
            }
            #just ensure to leave a combined total of 3**(k-1) or less in the H and L bins
            my $hl = 3**($k-1);
            my @hl = counterfeit_coin_selecthl(@$H + @$L - $hl, $H, $L, $G);
            push @next, counterfeit_coin_weight_hlg(\@hl, $H, $L, $G, $node);
        }
    }
    return @next;
}

sub counterfeit_coin_print {
    my ($node, $indent) = @_;
    $indent ||= 0;
    my ($NE, $NG, $NH, $NL, $NC) = @$node;
    print ('|' . (' ' x $indent) . (join ',', @$NE) . '; ' . (join ',', @$NG) . '; ' . (join ',', @$NH) . '; ' . (join ',', @$NL) . '; ' . $/);
    if($NC and @$NC){
        print '|' . (' ' x ($indent + 2)) . '(weight ' . (join ',', @{$NC->[0]{weight}}) . ')' . $/;
        for my $child (@$NC){
            counterfeit_coin_print($child->{node}, $indent + 2);
        }
    }
}

sub counterfeit_coin_check {
    my ($x, $n, $root) = @_;
    my ($E, $G, $H, $L, $children) = @{$root};
    if(@$E + @$G + @$H + @$L != $n){
        print 'something wrong3 at ' . $x . $/;
        print Dumper($E);
        print Dumper($G);
        print Dumper($H);
        print Dumper($L);
        exit;
    }
    if($children){
        map {counterfeit_coin_check($x, $n, $_->{node});} @$children;
    }
}

sub counterfeit_coin {
    my ($n, $x) = @_;
    if($n > counterfeit_coin_max($x)){
        print "This is just impossible." . $/;
        return;
    }
    #initialize
    my (@E, @G, @H, @L, @next);
    @E = 1 .. $n;
    my $root = [\@E, \@G, \@H, \@L];
    push @next, $root;
    while($x > 0){
        @next = counterfeit_coin_branch($x, @next);
        counterfeit_coin_check($x, $n, $root);
        $x--;
    }
    counterfeit_coin_print($root);
}

print header(-type=>'text/html',-charset=>'utf8');

my $cgi = CGI->new;
if($cgi->param('n') and $cgi->param('x')){
    counterfeit_coin($cgi->param('n'), $cgi->param('x'));
}
else{
    print "invalid parameter";
}

参考:

The Counterfeit Penny Problem

 

您的回答





不是您要找的问题? 浏览其他含有标签 的问题或者 自己问个.