Google Code Jam 2013 R1B - 坠落钻石

时间:2013-05-05 20:42:50

标签: c# algorithm

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

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

上述要求基本上归结为钻石逐渐形成较大的金字塔,一次一层。

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

一般来说,我的方法是找到需要多少层才能有一个包含坐标的层。一旦我知道我正在查看哪一层,我就可以确定一些与我们试图达到的图层和点相关的值。例如,当这一层是空的时,金字塔中有多少颗钻石,在其他方面强制使用之前可以在一侧堆叠多少颗钻石,有多少钻石必须在同一方向上滑动以达到所需的点,等等。

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

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

一旦我知道有多少滴与成功相关,以及为了掩盖这一点必须以同样的方式打破的数量,我总结了必要数量或更多的概率将采用相同的方式。< / p>

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

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();

    }
}

2 个答案:

答案 0 :(得分:3)

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

让我描述一下我是如何解决这个问题的。我们正在寻找金字塔。根据金字塔的钻石数量,可以为这些金字塔分配一层。图层1的金字塔只有1个钻石。图层2的金字塔有1 + 2 + 3个钻石。图层3的金字塔有1 + 2 + 3 + 4 + 5个钻石。图层n的金字塔有1 + 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 =&gt;对于每颗钻石,每一边的概率为0.5。这种情况与前一种情况没有区别。确切地说,这种情况的概率是pow( 0.5, 6 )。有4种不同的情况(rllllrlrlllrllrllrlllrlr)。这样有10种不同的案例。案例数是从5:binomial_coefficient( 5, 2 ) = 10
  • 中选择一个元素的方式数
  • l r l l l r =&gt;最后一颗钻石将落在右侧,因为左侧是满的。右侧的最后概率为1,左侧的概率为0。确切地说,这种情况的概率是pow( 0.5, 5 )。有4种不同的情况:binomial_coefficient( 4, 1 ) = 4
  • l l l l r r =&gt;最后两颗钻石将落在右侧,因为左侧是满的。最后两个概率为右侧为1,左侧为0。确切地说,这种情况的概率是pow( 0.5, 4 )。只有一个这样的情况,因为binomial_coefficient( 3, 0 ) = 1

一般算法假设最后0, 1, 2, 3, ..., nr元素不可避免地会向右移动,然后计算每种情况的概率(最后0, 1, 2, 3, ..., nr概率将为{{ 1}})并将每个概率乘以最后1概率为0, 1, 2, 3, ..., nr的不同情况的数量。

请参阅以下代码。 1将是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)的所有情况,其中k是一侧的最小钻石数量,所需的点是仍然被覆盖。

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

答案 1 :(得分: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>';
}