反复消除完美正方形后还剩下多少?

时间:2012-01-17 20:09:01

标签: algorithm math

我在Topcoder中练习SRM问题。我遇到了这个问题

  

问题陈述:今天是平安夜。世界各地的人们   庆祝这个假期。以下故事发生在。的土地上   驯鹿,圣诞老人居住的地方。

     

驯鹿喜欢糖果。他们有n块糖果。的碎片   糖果编号为1到n。 Dasher是驯鹿之一。他   我想吃一个糖果。要选择他会吃的那个,Dasher   使用以下方法:虽然有多个   糖果:丢弃所有用完美方块编号的糖果(即   糖果1,4,9,16,25等)。重拍其余的糖果1   通过k,保持数字的顺序相同。曾经只有一件   糖果仍然存在,Dasher会吃掉它。

     

给你一个int n。您的方法必须计算并返回数字   最初分配给Dasher吃的那块糖果。

我使用ArrayList解决了这个问题但是我的解决方案因为非常大的数量而失败(Java Heap Sapce Exception)。因此我在考虑是否有可能解决O(1)空间复杂性中的问题。

请提出您的建议和方法。我不想要代码,请只解释解决这个问题的逻辑。

我已经重新打开了这个问题 问题陈述,因此Stackoverflow中的大师可以帮助我解决 O(1)空间复杂度

5 个答案:

答案 0 :(得分:9)

另一种变体:

a = floor(sqrt(N-1))
b = min((N-1)/a, a+1)
solution = a*b+1

或者,换句话说,

unsigned long long eats(unsigned long long N) {
    unsigned long long r = (unsigned long long)sqrt(N-1);
    while(r*r >= N) --r;
    while(r*(r+2) < N) ++r;
    if (N <= r*(r+1)) {
        return r*r+1;
    }
    return r*(r+1)+1;
}

证据来自于分析next函数,该函数给出了任何糖果next(n*n) = 0的下一个位置,因此它不是部分函数。如果a*a < N < (a+1)*(a+1),我们有next(N) = N - a。因此,n = a*(a+1) + 1形式的一些形式

a*(a+1)+1 -> a*a + 1 -> (a-1)*a + 1 -> ... -> 2*3 + 1 ->2*2 + 1 -> 1*2 + 1 -> 1*1 + 1 -> 0*1 + 1

我们也看到a*a +1形式的数字也达到1.任何其他形式的数字在某个时刻达到大于1的正方形:

a*(a+1) -> a*a -> eliminated
a*(a+1) + r -> a*a + r -> (a-1)*a + r

代表2 <= r <= a。如果r = a(a-1)*a + r = a*a是正方形,则会立即消除。如果r < a,则两个步骤后达到的数字具有相同的r形式。接下来,数字达到

(r+1)*(r+2) + r -> (r+1)*(r+1) + r -> r*(r+1) + r -> r*r + r -> r*r -> elimination.

所以我们看到了

  • 当且仅当格式为n*n + 1n*(n+1) + 1
  • 时,数字才能到达过程中的第1点

到达以N糖果开头的第一个地点的最后一个数字当然是不超过N的最大数量。 QED。

答案 1 :(得分:5)

除非我犯了一个愚蠢的错误,否则有一个公式。它可能会被简化,但这是我遇到的第一个。

from math import floor, sqrt, ceil

def is_square(i):
    sq = int(i**0.5)
    return i == sq*sq

def brute(n):
    seq = range(1, n+1)
    while len(seq) > 1:
        seq = [x for i,x in enumerate(seq, 1) if not is_square(i)]
    return seq[0]

def dasher(n):
    w = lambda i: floor(sqrt(4*i+1))-1
    q = lambda i: ceil((i**2+3)/4)
    return q(w(n-1)+1)

并检查:

>>> b = [brute(i) for i in range(1, 10**3)]
>>> d = [dasher(i) for i in range(1, 10**3)]
>>> b[:25]
[1, 2, 3, 3, 5, 5, 7, 7, 7, 10, 10, 10, 13, 13, 13, 13, 17, 17, 17, 17, 21, 21, 21, 21, 21]
>>> b == d
True

答案 2 :(得分:4)

我想我可能会在这里做点什么。

f(n) = 1 if n = 1
f(n) = f(n-floor(sqrt(n))) + floor(sqrt(n)) if n is not a perfect square
f(n) = f(n-1) if n is a perfect square

基本情况很清楚。 “完美正方形”的情况来自于观察,如果n是一个完美的正方形,当你消除完美的正方形时它将被消除,所以求解它相当于解决比它更小的一个,无论如何。最后一个来自观察到在移除地板(sqrt(n))完美正方形和重新编号后,你已经将一些数字向左移动(但是否则答案是相同的)。我们可以检查前几个案例......

 n Answer f(n)
 1      1    1
 2      2    f(2-1) + 1 = f(1) + 1 = 1 + 1 = 2
 3      3    f(3-1) + 1 = f(2) + 1 = 2 + 1 = 3
 4      3    f(4-1) = f(3) = 3
 5      5    f(5-2) + 2 = f(3) + 2 = 3 + 2 = 5

证明这一点,如果它是正确的,应该是一个简单的延伸,直到我完成它,我把它留作练习。您应该检查大量案例,看看它是否有效;如果它不起作用,我会删除它。

编辑:

我认为我注意到的趋势及其起作用的原因是,对于非平方n,答案永远不会小于n的最大平方。我认为这样做的原因是在删除小于或等于m ^ 2的所有内容之前,你永远不能删除m ^ 2 + 1。鉴于此,上述复发关系几乎是微不足道的。

答案 3 :(得分:0)

您知道最后一个号码将被特殊号码1删除。如果您生成的所有号码都被删除1,您将拥有一个包含特殊号码的号码。所以你要做的就是生成这些数字。

让我们看看是否有模式。

设n为150

数字被1删除

  

r = [1,2,3,5,7,10,13,17,21,26,31,37,43,50,57,65,73,82,91,101,111,122, 133]

r [i + 1] -r [i]

的数组
  

[1,1,2,2,3,3,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11]

数字被2删除

  

r = [4,6,8,11,14,18,22,27,32,38,44,51,58,66,74,83,92,102,112,123,134,146]

r [i + 1] -r [i]

的数组
  

[2,2,3,3,4,4,5,5,6,7,7,8,8,9,9,10,10,11,11,12]

数字被9删除 我们知道9中删除的数字在前2个元素中会有3个不同,第一个元素是9.从中我们可以生成在3,3,4,4,5这个模式后删除的数字,五     [9,12,15,19,23,28,33,39,45,52,59,67,75,84,93,103,113,124,135,147]     [3,3,4,4,5,5,6,7,7,8,8,9,9,10,10,11,11,12]

import math

def getSpecial(n):
    sp = list()
    s = 1
    while((s * s) <= n):
        sp.append(s*s)
        s += 1
    return sp

def bruteForce(n):
    nu = range(n+1)
    nu.pop(0)
    while(len(nu) > 1):
        sp = getSpecial(len(nu))
        removed = list()
        for x in sp[::-1]:
            removed.append(nu.pop(x-1))
    return nu[0]

def fancyMathWitchCraft(n):
    sp = getSpecial(n)    
    oneset = [1]
    j = 0.0
    while(oneset[-1] <= n):
        oneset.append( oneset[-1] + int(1 + 1 * math.floor(j/2)) )
        j = j + 1.0

    if(oneset[-1] <= n):
        return oneset[-1]
    if(oneset[-2] <= n):
        return oneset[-2]
    if(oneset[-3] <= n):
        return oneset[-3]



def main():
    for x in range(1,2000):
        if(bruteForce(x) != fancyMathWitchCraft(x)):
            print(x, bruteForce(x), fancyMathWitchCraft(x))
    print("Done")

if __name__ == "__main__":
    main()

这背后的证据可能是最后一个完美的sq只会删除1个数字,所以最终的数字将来自最大的连续片段,它将在第一次迭代后不受影响,这将是最后一个分割。如果你真的想要一个数学证明,你必须把这个问题带到meta.stackoverflow

答案 4 :(得分:-1)

n=1, eats=1  
n=2, eats=2  
n=3, eats=3  
n=4, eats=3  
n=5, eats=5  
...  

你看到一种模式出现了吗?提出一个公式,并使用mathematical induction

证明公式是正确的

这是c ++代码:

#include <iostream>
#include <cmath>

using namespace std;

bool is_perfect_square(int value)
{       
    return pow( static_cast<double>(static_cast<int>(sqrt(static_cast<double>(value)))), 2.0) == value;
}

int EatEm(int n)
{
    while (is_perfect_square(n))
    {
        n -= (static_cast<int>(sqrt(static_cast<double>(n)) - 1));
    }

    return n;
}

int main()
{           
    int res = EatEm(25);
    cout << res << endl;
    return 0;
}