Algorithm 如何计算具有特定属性的大A和B之间的整数?

Algorithm 如何计算具有特定属性的大A和B之间的整数?,algorithm,dynamic-programming,Algorithm,Dynamic Programming,在编程竞赛中,许多任务都会出现以下模式: 给定巨大的数字A和B(可能是20个十进制数字或更多),用A确定整数X的数量≤ X≤ 具有一定性质P的 为了练习 其中,有趣的属性示例包括: “X的数字和是60” “X仅由数字4和7组成” “X是回文的”,这意味着X的十进制表示形式等于其倒数(例如,X=1234321) 我知道如果我们把f(Y)定义为这样的整数X的个数≤ Y、 那么我们问题的答案是f(B)-f(A-1)。简化的问题是如何有效地计算函数f。在某些情况下,我们可以利用某些数学性质得出一个公

在编程竞赛中,许多任务都会出现以下模式:

给定巨大的数字A和B(可能是20个十进制数字或更多),用A确定整数X的数量≤ X≤ 具有一定性质P的

为了练习

其中,有趣的属性示例包括:

  • “X的数字和是60”
  • “X仅由数字4和7组成”
  • “X是回文的”,这意味着X的十进制表示形式等于其倒数(例如,X=1234321)
我知道如果我们把f(Y)定义为这样的整数X的个数≤ Y、 那么我们问题的答案是f(B)-f(A-1)。简化的问题是如何有效地计算函数f。在某些情况下,我们可以利用某些数学性质得出一个公式,但这些性质往往更为复杂,我们在比赛中没有足够的时间

是否有一种更通用的方法在很多情况下都有效?它还可以用于枚举具有给定属性的数字或计算它们的聚合吗


另一种方法是找到具有给定属性的第k个数,这当然可以通过使用二进制搜索和计数函数来解决。

事实上,有一种方法可以解决这个问题,结果证明这种方法非常有效。它还可以用于枚举具有给定属性的所有X,前提是它们的数量相当小。您甚至可以使用它来聚合具有给定属性的所有X上的一些关联运算符,例如,找到它们的和

为了理解一般的想法,让我们试着表述条件X≤ 用X和Y的函数表示Y

假设我们有X=x1x2。。。xn-1 xn和Y=y1 y2。。。Yn—1 yn,其中席和彝是x和Y.的十进制数字,如果数字有不同的长度,我们总是可以在较短的一个前面加零个数字。

让我们定义“代码”>左ToMistSt*Lo/C>作为席i的最小i。如果没有这样的i,我们将最左边的lo定义为n+1。 类似地,我们定义了<代码>左ToMoSTythHi/Cu>作为最小席i,或者X+I,否则N+1。< /P> 现在X≤ 如果且恰好如果

leftmost_lo 60:return 0,则Y为真
res=0
对于d:=0到(lo?9:y[i]):
res+=f(i+1,和+d,lo | d

可以说,这种看待它的方式有点简单,但也比
最左端的loo
/
最左端的hi
方法不那么明确。对于回文问题等更复杂的情况,它也不能立即起作用(尽管它也可以在那里使用)。

@JuanLopes:是的。我的想法是记录你的想法和结果,这样其他人就可以从中获益,就像博客一样:)我还试图让有竞争力的编程社区对Stack Overflow更感兴趣,所以这可能有助于树立一个榜样,像这样的问题实际上是受欢迎的,并且是这里的主题(显然,我希望其他人也会觉得这很有趣)@JuanLopes:我也愿意接受其他问题的答案course@arunmoezhi你的意思是,自我回答的问题?因为这些问题都是在SO中出现的(在“提问”对话框中甚至有一个复选框,允许你在发布问题之前写下答案).还是说竞争性编程?我想我刚刚发明了一个标签now@Charles:或者换句话说,这是一个非常有竞争力的编程特定问题,因为它描述了一个仅在该设置中有用而在“真实世界”中不有用的技巧@RobNeuhaus:公平地说,80%与编程竞赛相关的问题也很糟糕。问题是它们的有趣之处。我认为如果更多来自竞争性编程社区的人开始在这里提出高质量的问题,而不仅仅是“为什么我的SPOJ FOO代码会给出WA?”,这将真正有助于
算法使用属性X计算整数X的个数f(Y)≤ Y和X的数字和为60
看起来它的复杂度是O(10^n)。我看不出它是O(n^4)。递归级别可以上升到n,每个递归中都有一个从0到9的循环。我是不是遗漏了什么?正如我所说的,你需要添加回忆录来加快速度。只有O(n^4)不同的参数赋值,因此函数体最多执行O(n^4)次,每次只执行递归调用以外的常量工作(0到9的循环可以被视为常量时间)。或者换句话说,有10 ^n个分支,但只有O(n^4)这些问题实际上都是不同的子问题。因为我们不会多次解一个分支,递归树中只有O(n^4)个节点,所以O(10^n)的复杂度可以通过记忆减少到O(n^4)。如何使它成为O(n^2)或更好?@NiklasB。到目前为止,你不是必须同时对i和sum_进行记忆,直到它变成O(n*n)如果我是,请纠正我wrong@harish.venkat是的,但在这种情况下,sum_到目前为止是以常数60为界的
count(i, sum_so_far, leftmost_lo, leftmost_hi):
    if i == n + 1:
        # base case of the recursion, we have recursed beyond the last digit
        # now we check whether the number X we built is a valid solution
        if sum_so_far == 60 and leftmost_lo <= leftmost_hi:
            return 1
        else: 
            return 0
    result = 0
    # we need to decide which digit to use for x[i]
    for d := 0 to 9
        leftmost_lo' = leftmost_lo
        leftmost_hi' = leftmost_hi
        if d < y[i] and i < leftmost_lo': leftmost_lo' = i
        if d > y[i] and i < leftmost_hi': leftmost_hi' = i
        result += count(i + 1, sum_so_far + d, leftmost_lo', leftmost_hi')
    return result
count(i, leftmost_lo, leftmost_hi):
    if i == ceil(n/2) + 1: # we stop after we have placed one half of the number
        if leftmost_lo <= leftmost_hi:
            return 1
        else: 
            return 0
    result = 0
    start = (i == 1) ? 1 : 0    # no leading zero, remember?
    for d := start to 9
        leftmost_lo' = leftmost_lo
        leftmost_hi' = leftmost_hi
        # digit n - i + 1 is the mirrored place of index i, so we place both at 
        # the same time here
        if d < y[i]     and i     < leftmost_lo': leftmost_lo' = i
        if d < y[n-i+1] and n-i+1 < leftmost_lo': leftmost_lo' = n-i+1
        if d > y[i]     and i     < leftmost_hi': leftmost_hi' = i
        if d > y[n-i+1] and n-i+1 < leftmost_hi': leftmost_hi' = n-i+1
        result += count(i + 1, leftmost_lo', leftmost_hi')
    return result
def f(i, sum_so_far, lo):
    if i == n + 1: return sum_so_far == 60
    if sum_so_far > 60: return 0
    res = 0
    for d := 0 to (lo ? 9 : y[i]):
         res += f(i + 1, sum + d, lo || d < y[i])
    return res