2

昨天 Code Jam 有一个题为 Falling Diamonds 的问题。全文可以在这里找到,但总结如下:

  • 钻石沿 Y 轴落下。
  • 如果一颗钻石与另一颗钻石点对点,它有 50/50 的机会向右或向左滑动,前提是它没有被阻挡。
  • 如果钻石被阻止向一个方向滑动,它总是会向另一个方向滑动。
  • 如果钻石在两个方向上都被阻挡,它将停止并停留在阻挡钻石上。
  • 如果钻石撞到地面,它会埋到一半,然后停下来。
  • 钻石的方向永远不会改变,即它会滑动或下沉,但不会翻滚。
  • 目标是找到一颗钻石停留在给定坐标的概率,假设 N 颗钻石掉落。

上述要求基本上归结为钻石依次建造更大的金字塔,一次一层。

可以说,我无法让谷歌满意地解决这个问题。我从问题描述中得到了正确的样本,但在实际输入文件中失败了。理想情况下,我希望看到一个匹配的输入和正确的输出文件,我可以使用它来尝试找出我的错误。除此之外,我也欢迎对我的代码发表评论。

一般来说,我的方法是找出需要多少层才能拥有一个包含坐标的层。一旦我知道我在看哪一层,我就可以确定与我们试图到达的层和点相关的一些值。例如,当这一层为空时,金字塔中有多少颗钻石,有多少颗钻石可以堆叠在一侧,其余的则被迫相反,有多少必须向同一方向滑动才能到达所需的点等。

然后,我检查掉落的钻石数量是否导致无法到达该点(概率 0),或者保证我们将覆盖该点(概率 1)。挑战在于有可能但不能保证的中间地带。

对于中间地带,我首先检查我们是否下降到足以填充一侧并迫使剩余的下降向相反方向滑动。原因是在这种情况下,我们可以保证一定数量的钻石会滑到每一边,减少了我们需要担心的掉落数量,解决了边满时概率变化的问题。示例:如果 12 颗钻石掉落,则保证外层的每一侧都会有 2 颗或更多颗钻石,给定的一侧是否有 2、3 或 4 颗取决于仅 2 颗掉落的结果,而不是全部 6 颗的结果落在这一层。

一旦我知道有多少滴与成功相关,以及必须以相同的方式打破以覆盖这一点的数字,我就会总结必要的数字或更多的概率会以同样的方式发生。

正如我所说,我可以解决问题描述中的示例,但我没有得到输入文件的正确输出。不幸的是,我找不到任何东西告诉我正确的输出是什么,以便我可以将它与我得到的进行比较。这是我的代码(自从比赛结束以来,我已经花了相当多的时间试图调整它以获得成功并添加评论以防止自己迷路):

protected string Solve(string Line)
{
    string[] Inputs = Line.Split();
    int N = int.Parse(Inputs[0]);
    int X = int.Parse(Inputs[1]);
    int Y = int.Parse(Inputs[2]);

    int AbsX = X >= 0 ? X : -X;
    int SlideCount = AbsX + Y;  //number that have to stack up on one side of desired layer in order to force the remaining drops to slide the other way.
    int LayerCount = (SlideCount << 1) | 1; //Layer is full when both sides have reached slidecount, and one more drops
    int Layer = SlideCount >> 1; //Zero based Index of the layer is 1/2 the slide count
    int TotalLayerEmpty = ((Layer * Layer) << 1) - Layer; //Total number of drops required to fill the layer below the desired layer
    int LayerDrops = N - TotalLayerEmpty; //how many will drop in this layer
    int MinForTarget; //Min number that have to be in the layer to hit the target location, i.e. all fall to correct side
    int TargetCovered; //Min number that have to be in the layer to guarantee the target is covered
    if (AbsX == 0)
    {//if target X is 0 we need the layer to be full for coverage (top one would slide off until both sides were full)
        MinForTarget = TargetCovered = LayerCount;
    }
    else
    {
        MinForTarget = Y + 1; //Need Y + 1 to hit an altitude of Y
        TargetCovered = MinForTarget + SlideCount; //Min number that have to be in the layer to guarantee the target is covered
    }

    if (LayerDrops >= TargetCovered)
    {//if we have enough dropping to guarantee the target is covered, probability is 1
        return "1.0";
    }
    else if (LayerDrops < MinForTarget)
    {//if we do not have enough dropping to reach the target under any scenario, probability is 0
        return "0.0";
    }
    else
    {//We have enough dropping that reaching the target is possible, but not guaranteed

        int BalancedDrops = LayerDrops > SlideCount ? LayerDrops - SlideCount : 0; //guaranteed to have this many on each side
        int CriticalDrops = LayerDrops - (BalancedDrops << 1);//the number of drops relevant to the probablity of success
        int NumToSucceed = MinForTarget - BalancedDrops;//How many must break our way for success
        double SuccessProb = 0;//Probability that the number of diamonds sliding the same way is between NumToSucceed and CriticalDrops
        double ProbI;
        for (int I = NumToSucceed; I <= CriticalDrops; I++)
        {
            ProbI = Math.Pow(0.5, I); //Probability that I diamonds will slide the same way
            SuccessProb += ProbI;
        }

        return SuccessProb.ToString();

    }
}
4

2 回答 2

3

您的一般方法似乎适合该问题,尽管最后概率的计算并不完全正确。

让我描述一下我是如何解决这个问题的。我们正在研究金字塔。根据金字塔有多少钻石,可以为这些金字塔分配一个层。层金字塔1只有1钻石。层金字塔21 + 2 + 3钻石。层金字塔31 + 2 + 3 + 4 + 5钻石。层金字塔n1 + 2 + 3 + ... + 2*n-1菱形,等于(2 * n - 1) * n

鉴于此,我们可以计算出我们能够用给定数量的钻石建造的最大金字塔的层数:

layer = floor( ( sqrt( 1 + 8 * diamonds ) + 1 ) / 4 )

以及建造这个金字塔不需要的钻石数量。这些钻石将开始填充下一个更大的金字塔:

overflow = diamonds - layer * ( 2 * layer - 1 )

我们现在可以看到以下内容:

  • 如果该点在层内layer,它将被覆盖,所以p = 1.0
  • 如果该点不在层内layer + 1(即下一个更大的金字塔),它将不会被覆盖,所以p = 0.0.
  • 如果该点在层内layer + 1,则可能会被覆盖,所以0 <= p <= 1

由于我们只需要解决最后一个问题,我们可以稍微简化一下问题陈述:给定三角形的两条边,rl。每一面都有一个固定的容量,它可以带走的最大钻石数量。一种配置的概率是多少(nr, nl),其中nr表示右侧的菱形,nl表示左侧的菱形,nr + nl = overflow

这个概率可以使用伯努利轨迹计算:

P( nr ) = binomial_coefficient( overflow, k ) * pow( 0.5, overflow )

但是,这在一种情况下会失败:如果一侧完全被钻石填充,则概率会发生变化。现在钻石落在完全填充的一侧的概率是0,而另一侧的概率是1

假设以下情况:每边最多可以拿 4 颗钻石,而还剩下 6 颗钻石。有趣的情况是现在P( 2 ),因为在这种情况下,左侧将取 4 颗钻石。

6 颗钻石如何掉落的一些示例。r代表决定向右走,而l代表向左走

  • l r l r l l => 对于每颗钻石,每边的概率为0.5. 本案与前案无异。这种情况的概率是pow( 0.5, 6 )。有 4 种不同的情况(rllllr, lrlllr, llrllr, lllrlr)。像这样的情况有10种。案例数是可以从 5 种中选择一个元素的方式数:binomial_coefficient( 5, 2 ) = 10
  • l r l l l r => 最后一颗钻石将落在右侧,因为左侧已满。最后一个概率是右侧为 1,左侧为 0。这种情况的概率是pow( 0.5, 5 )。像这样的有4种不同的情况:binomial_coefficient( 4, 1 ) = 4
  • l l l l r r => 最后两颗钻石将落在右侧,因为左侧已满。最后两个概率是右侧为 1,左侧为 0。这种情况的概率是pow( 0.5, 4 )。这样的情况只有一种,因为binomial_coefficient( 3, 0 ) = 1.

一般算法是假设最后一个0, 1, 2, 3, ..., nr元素不可避免地会向右移动,然后计算每个案例的概率(最后一个0, 1, 2, 3, ..., nr概率是),并将每个概率乘以最后一个概率1的不同案例的数量0, 1, 2, 3, ..., nr1

请参阅以下代码。p将是nr钻石在右侧且左侧已满的情况下的概率:

p = 0.0
for i in range( nr + 1 ):
    p += pow( 0.5, overflow - i ) * binomial_coefficient( overflow - i - 1, nr - i )

现在我们可以计算每个单独组合的概率(nr, nl),可以简单地将所有情况相加nr > k,其中 k 是仍然覆盖所需点的一侧的最小菱形数。

请参阅我用于此问题的完整 python 代码:https ://github.com/frececroka/codejam-2013-falling-diamonds/blob/master/app.py

于 2013-05-06T15:03:07.040 回答
1

你的假设过于简单。您可以下载使用我的解决方案计算的大型数据集的正确答案:

http://pastebin.com/b6xVhp9U

您必须计算所有可能占据您兴趣点的钻石组合。为此,我使用了以下公式:

https://math.stackexchange.com/a/382123/32707

你基本上必须:

  • 计算金字塔的高度(即计算 FIXED 菱形)
  • 计算可以左右自由移动的方块数
  • 计算概率(使用二项式系数的总和)

使用后者和 Y 点,您可以应用该公式来计算概率。

如果您无法解决此问题,也请不要担心,因为它非常困难。如果你想要我在 PHP 中的解决方案,它是:

请注意,您必须计算该点是否在固定金字塔内或在固定金字塔外,您还必须进行其他次要检查。

<?php


set_time_limit(0);


$data = file('2bl.in',FILE_IGNORE_NEW_LINES);


$number = array_shift($data);

for( $i=0;$i<$number;$i++ ) {

    $firstLine = array_shift($data);
    $firstLine = explode(' ',$firstLine);


    $s = $firstLine[0];
    $x = $firstLine[1];
    $y = $firstLine[2];

    $s = calcCase( $s,$x,$y  );
    appendResult($i+1,$s);

}


function calcCase($s,$x,$y) {

    echo "S: [$s] P($x,$y)\n<br>";

    $realH = round(calcH($s),1);
    echo "RealHeight [$realH] ";

    $h = floor($realH);
    if (isEven($h))
        $h--;

    $exactDiamonds = progression($h);
    movableDiamonds($s,$h,$exactDiamonds,$movableDiamonds,$unfullyLevel);   


    $widthLevelPoint = $h-$y;


    $spacesX =  abs($x) - $widthLevelPoint;

    $isFull = (int)isFull($s,$exactDiamonds);



    echo "Diamonds: [$s], isFull [$isFull], Height: [$h], exactDiamonds [$exactDiamonds], movableDiamonds [$movableDiamonds], unfullyLevel [$unfullyLevel] <br> 
         widthlevel [$widthLevelPoint], 
         distance from pyramid (horizontal) [$spacesX]<br> ";


    if ($spacesX>1)
        return '0.0';

    $pointHeight = $y+1;


    if ($x==0 && $pointHeight > $h) {
        return '0.0';
    }


    if ($movableDiamonds==0) { 

        echo 'Fixed pyramid';

        if ( $y<=$h && abs($x) <= $widthLevelPoint )    
            return '1.0';
        else
            return '0.0';

    } 



    if ( !$isFull ) {

        echo "Pyramid Not Full ";


        if ($spacesX>0)
            return '0.0';


        if ($unfullyLevel == $widthLevelPoint)  
            return '0.5';


        else if ($unfullyLevel > $widthLevelPoint)
            return '0.0';


        else
            return '1.0';

    }


    echo "Pyramid full";


    if ($spacesX<=0)
        return '1.0';

    if ($movableDiamonds==0)
        return '0.0';




    if ( $movableDiamonds > ($h+1) ) {

        $otherDiamonds = $movableDiamonds - ($h+1);
        if ( $otherDiamonds - $pointHeight >= 0  ) {

            return '1.0';
        }


    }

    $totalWays = totalWays($movableDiamonds);
    $goodWays = goodWays($pointHeight,$movableDiamonds,$totalWays);

    echo "<br>GoodWays: [$goodWays], totalWays: [$totalWays]<br>";


    return sprintf("%1.7f",$goodWays / $totalWays);
}



function goodWays($pointHeight,$movableDiamonds,$totalWays) {


    echo "<br>Altezza punto [$pointHeight] ";

    if ($pointHeight>$movableDiamonds)
        return 0;


    if ( $pointHeight == $movableDiamonds )
        return 1;

    $good = sumsOfBinomial( $movableDiamonds, $pointHeight );

    return $good;
}

function totalWays($diamonds) {
    return pow(2,$diamonds);    
}


function sumsOfBinomial( $n, $k ) {

    $sum = 1;   //> Last element (n;n)
    for($i=$k;$i<($n);$i++) {

        $bc =  binomial_coeff($n,$i);
        //echo "<br>Binomial Coeff ($n;$i): [$bc] ";

        $sum += $bc;
    }

    return $sum;
}


// calculate binomial coefficient
function binomial_coeff($n, $k) {

  $j = $res = 1;

  if($k < 0 || $k > $n)
     return 0;
  if(($n - $k) < $k)
     $k = $n - $k;

  while($j <= $k) {
    $res = bcmul($res, $n--);
    $res = bcdiv($res, $j++);
  }

  return $res;

}

function isEven($n) {
    return !($n&1); 
}

function isFull($s,$exact) {
    return ($exact <= $s);
}

function movableDiamonds($s,$h,$exact,&$movableDiamonds,&$level) {

    $baseWidth = $h;
    $level=$baseWidth;

    //> Full pyramid
    if ( isFull($s,$exact) ) {
        $movableDiamonds = ( $s-$exact );
        return;
    }


    $movableDiamonds = $s;

    while( $level ) {

        //echo "<br> movable [$movableDiamonds] removing [$level] <br>" ;

        if ($level > $movableDiamonds)  
            break;

        $movableDiamonds = $movableDiamonds-$level;
        $level--;
        if ($movableDiamonds<=0)
            break;
    }

    return  $movableDiamonds;

}


function progression($n) {
    return (1/2 * $n *(1+$n) );
}

function calcH($s) {

    if ($s<=3)
        return 1;

    $sqrt = sqrt(1+(4*2*$s));
    //echo "Sqrt: [$sqrt] ";

    return ( $sqrt-1 ) / 2;
}





function appendResult($caseNumber,$string) {
    static $first = true;

    //> Cleaning file
    if ($first) {
        file_put_contents('result.out','');
        $first=false;
    }

    $to = "Case #{$caseNumber}: {$string}";
    file_put_contents( 'result.out' ,$to."\n",FILE_APPEND); 
    echo $to.'<br>';
}
于 2013-05-05T21:19:18.960 回答