【问题标题】:Find the number of subsequences of a n-digit number, that are divisible by 8找出能被 8 整除的 n 位数的子序列数
【发布时间】:2017-05-26 14:36:59
【问题描述】:

给定 n = 1 到 10^5,以十进制格式存储为字符串。

示例:如果 n = 968,则在所有子序列(即 9、6、8、96、68、98、968)中有 3 个子序列,即 968、96 和 8,它们可以被 8 整除. 所以,答案是 3。

由于答案可能非常大,因此以 (10^9 + 7) 为模打印答案。

【问题讨论】:

  • 到目前为止你尝试过什么?它有一个动态编程标签。您对状态和转换可能是什么有想法吗?
  • 如果字符串是88,答案是什么?是 3(8、8 和 88)还是 2(8 只计数一次)?
  • 它们真的是subsequences,还是子字符串?可以有2^(10^5) 子序列,数量不少……
  • @VincentvanderWeele 你从哪里得到的?大多数 10^5 以下的数字都是 4 位数字,因此只有 2^4 个子序列。这不是很多。
  • @btilly 标题说这是一个 n 位数字,其中 n 可以达到 100000,对吧?

标签: string algorithm time-complexity dynamic-programming combinatorics


【解决方案1】:

您可以使用动态编程。令f(len, sum) 为长度为len 的前缀的子序列数,使得它们的和为sum 模8(sum 的范围为0 到7)。

flen = 1 的价值是显而易见的。过渡如下:

  1. 我们可以在新位置开始一个新的子序列:f(len, a[i] % 8) += 1

  2. 我们可以从较短的前缀继续任何子序列:

    for old_sum = 0..7
         f(len, (old_sum * 10 + a[i]) % 8) += f(len - 1, old_sum) // take the new element
         f(len, old_sum) += f(len - 1, old_sum) // ignore the new element
    

当然,您可以执行所有计算模块 10^9 + 7 并使用标准整数类型。

  1. 答案是f(n, 0)(所有元素都考虑在内,模8求和为0)。

此解决方案的时间复杂度为 O(n)(因为有 O(n) 状态和每个状态的 2 个转换)。

注意:如果数字不能有前导零,您可以只向状态添加一个参数:指示子序列的第一个元素是否为零的标志(此序列不应扩展)。解决方案的其余部分保持不变。

【讨论】:

  • 这将是子序列而不是子字符串 +1 的正确解决方案
【解决方案2】:

注意:此答案假设您的意思是连续的子序列。

一个数能被8整除的整除规则是该数的最后三位是否能被8整除。使用这个,可以得到一个简单的O(n)算法,其中n是数字中的数字。

  1. N=a_0a_1...a_(n-1)N 的十进制表示,n 数字。
  2. 设到目前为止的序列数为s = 0
  3. 对于每组三位数a_i a_(i+1) a_(i+2),检查该数字是否能被8整除。如果是这样,将i + 1 添加到序列数,即s = s + i。这是因为所有字符串a_k..a_(i+2) 都可以被8 整除,k 的范围为0..i
  4. 循环 i0n-2-1 并继续。

因此,如果您有1424968,则可整除的子序列位于:

  1. i=1424 产生 i+1 = 2 数字:4241424
  2. i=3496 产生 i+1 = 4 数字:496249642496142496
  3. i=4968 产生 i+1 = 5 数字:9684968249684249681424968

请注意,需要对长度小于三位数的数字进行一些小的修改。

因此序列总数 = 2 + 4 + 5 = 11。总复杂度 = O(n) 其中n 是位数。

【讨论】:

  • 是的,利用可分性规则比任何检查单个子序列的“幼稚”方法都容易
【解决方案3】:

这不是一个代码编写服务,所以我只会给你足够的开始。

子序列有 10 种可能的状态。第一种是空的。第二个是前导 0。其他 8 是一个正在进行的数字,即 0-7 mod 8。你从字符串的开头开始,有 1 种为空的方式,没有其他方式。在字符串的末尾,您的答案是前导 0 加上正在进行的数字 0 mod 8 的方式数。

转换表应该很明显。剩下的只是普通的动态规划。

【讨论】:

    【解决方案4】:

    可以使用以下事实:对于任何三位数字abc,以下成立:

    abc % 8 = ((ab % 8) * 10 + c) % 8
    

    或者换句话说:具有固定起始索引的数字的测试可以级联:

    int div8(String s){
        int total = 0, mod = 0;
    
        for(int i = 0; i < s.length(); i++)
        {
            mod = (mod * 10 + s.charAt(i) - '0') % 8
    
            if(mod == 0)
                total++;
        }
    
        return total;
    }
    

    但我们没有固定的起始指数! 好吧,这很容易解决:

    假设两个序列ab,这样int(a) % 8 = int(b) % 8ba的后缀。无论序列如何继续,ab 的模数将始终保持相等。因此,跟踪具有模 8 等值属性的序列的数量就足够了。

    final int RESULTMOD = 1000000000 + 7;
    
    int div8(String s){
        int total = 0;
        //modtable[i] is the number of subsequences with int(sequence) % 8 = i
        int[] modTable = new int[8];
    
        for(int i = 0; i < s.length(); i++){
            int[] nextTable = new int[8];
    
            //transform table from last loop-run (shared modulo)
            for(int j = 0; j < 8; j++){
                nextTable[(j * 10 + s.charAt(i) - '0') % 8] = modTable[j] % RESULTMOD;
            }
    
            //add the sequence that starts at this index to the appropriate bucket
            nextTable[(s.charAt(i) - '0') % 8]++;
    
            //add the count of all sequences with int(sequence) % 8 = 0 to the result
            total += nextTable[0];
            total %= RESULTMOD;
    
            //table for next run
            modTable = nextTable;
        }
    
        return total;
    }
    

    运行时是O(n)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多