【问题标题】:Summation of a number made up of 4 5 6由 4 5 6 组成的数的总和
【发布时间】:2015-09-30 16:06:22
【问题描述】:

我们得到三个整数 x、y 和 z。您必须找到所有数字的总和,其数字仅由 4、5 和 6 组成,十进制表示中最多有 x 个四位,十进制表示中最多有 y 个五位,十进制表示中最多有 z 个六位

我正在使用Describe Here这个概念

我的代码:

// fact[i] is i!
    for(int i=0;i<=x;i++)
        for(int j=0;j<=y;j++)
            for(int k=0;k<=z;k++){

           int t = i+j+k;
           if(t==0) continue;
           long ways = fact[t-1];
           long pow = (long) Math.pow(10,t-1);
           long rep=0;
           if(i!=0){
               rep = fact[j]*fact[k];
               if(i>0) rep*=fact[i-1];

              o+= 4*pow*(ways/rep); 
           }

           if(j!=0){
               rep = fact[i]*fact[k];
               if(j>0) rep*=fact[j-1];

              o+= 5*pow*(ways/rep); 
           }

           if(k!=0){
               rep = fact[i]*fact[j];
               if(k>0) rep*=fact[k-1];

              o+= 6*pow*(ways/rep); 
           }

        }

但我得到的x=1 , y=1 and z=1 的答案是错误的,我得到的是3315,而正确的答案是3675

请帮我找出我的错误。

4+5+6+45+54+56+65+46+64+456+465+546+564+645+654=3675

【问题讨论】:

  • @你能解释一下你在代码中使用的逻辑吗?
  • 我对此有不同的解决方案,它在 JAVA 中使用字符串。
  • @Dante 我使用了链接中描述的概念,使用排列
  • 奇怪的是,目前的答案都没有解决 OP 的问题,“请帮我找出我的错误。”
  • 这与stackoverflow.com/q/31285547/2336725 相关,虽然它有x=y=z=+Infinity

标签: algorithm math


【解决方案1】:

问题不在于您的代码,而在于您的逻辑:假设 S 是仅由数字 4、5 和 6 组成的数字集。您想要计算 SUM(S)。但由于您只考虑这些数字的第一个数字,因此您实际上是在计算 SUM(s in S, s - s % 10^floor(log10(s)))。

不过,你这样做是正确的,因为

4 + 5 + 6 + 40 + 50 + 50 + 60 + 40 + 60 + 400 + 400 
  + 500 + 500 + 600 + 600 = 3315

长话短说,您只需在下方申请用户גלעד ברקן's approach 即可修复您的代码。它将导致 O(xyz(x+y+z)) 算法,并且可以通过看到 SUM(i = 0 to t-1, 10^i) = (10^t - 1 来改进为 O(xyz) ) / 9,所以在你的代码中改变一行就足够了:

// was: long pow = (long) Math.pow(10,t-1);
long pow = (long) (Math.pow(10,t)-1) / 9;

还有一个非常简单的 O(xyz) 时间 + 空间方法,使用动态规划,只使用最少的数学和组合:让 g(x, y, z) 为元组 (count, sum),其中 count 是4-5-6-数字的数量,由 恰好 x 个四位、y 个五位和 z 个六位组成。 sum 是他们的总和。那么我们就有如下的递归:

using ll=long long;
pair<ll, ll> g(int x, int y, int z) {
    if (min(x,min(y,z)) < 0)
        return {0,0};
    if (max(x,max(y,z)) == 0)
        return {1,0};
    pair<ll, ll> result(0, 0);
    for (int d: { 4, 5, 6 }) {
        auto rest = g(x - (d==4), y - (d==5), z - (d==6));
        result.first += rest.first;
        result.second += 10*rest.second + rest.first*d;
    }
    return result;
}

int main() {
    ll res = 0;
    // sum up the results for all tuples (i,j,k) with i <= x, j <= y, k <= z
    for (int i = 0; i <= x; ++i)
        for (int j = 0; j <= y; ++j)
            for (int k = 0; k <= z; ++k)
                res += g(i, j, k).second;
    cout << res << endl;
}

我们可以向 g 添加 memoization 以避免计算结果两次,从而生成多项式时间算法,而不需要组合洞察力。

这很容易概括为您可以使用超过 3 个数字的情况,如 gen-y-s's answer 所示。它也适用于对数字形状有更复杂限制的情况。如果您想对给定范围内的数字求和,它甚至可以通过组合 with another generic DP approach 来概括。

编辑: 还有一种方法可以直接描述您的函数 f(x, y, z),即包含 的 4-5-6-数字的总和>最多 x个四人组,y个五人组和z个六人组。为此,您需要包含排除。例如,对于我们有的计数部分

c(x, y, z) = c(x-1,y,z) + c(x,y-1,z) + c(x,y,z-1) - c(x-1 ,y-1,z) - c(x-1,y,z-1) - c(x,y-1,z-1) + c(x-1,y-1,z-1)

总和稍微复杂一些。

【讨论】:

  • 为了理解这一点,我忽略了总和,只看计数,因为这应该更容易理解。你基本上是在说f(x,y,z).count == f(x-1,y,z).count + f(x,y-1,z).count + f(x,y,z-1).count。这是您的主张吗?当然是针对非零 x、y、z 的?我问是因为我认为这不正确,因为这三组之间有很多重叠。
  • @AaronMcDaid 嗯,你是对的,它不是那样工作的。我们必须删除“小于或等于”并使用“等于”
  • 简单来说,f(1,1,0) 的计数是 4。f(0,1,1)f(1,0,1) 的计数也是如此。但是f(1,1,1) 的计数是 8,而不是您的身份声称的 12。
  • @AaronMcDaid 现在应该修复了
  • 也许你现在可以调用你的函数g而不是f,因为它的含义已经改变了?
【解决方案2】:

在 Python 3 中:

def sumcalc(x,y,z):
  if x < 0 or y < 0 or z < 0: return -1
  import itertools
  sum = 0
  for i, j, k in itertools.product(range(x + 1), range(y + 1), range(z + 1)):
    e = (('4' * i) + ('5' * j) + ('6' * k))
    if e:
      perms = [''.join(p) for p in itertools.permutations(e)]  
      for i in set(perms): sum += int(i)
  return sum

这种方法很简单,可以与大多数任何编程语言一起使用,如果有的话,不一定包括类似的语法糖。基本步骤是:

  1. 对于给定的整数 x、y 和 z 全部 >= 0,为所有组合中的每一个写一个字符串,不考虑“4”从 0 到 x 出现的顺序,“5”从 0 到 y 出现和“ 6' 从 0 到 z 出现。 (但生成组合是为了确保完整性。)

  2. 对于 (1) 中生成的每个字符串,生成其字符的所有唯一且非空的排列。

  3. 对于(2)中产生的每个字符串排列,将其转换为整数并将其添加到总和中。

Python 3 整数具有无限精度,因此无需拖入 Long 或 BigInteger 类型来改进它。

【讨论】:

    【解决方案3】:

    你的逻辑几乎是正确的。您只是忘记了对于(i,j,k) 的每个配置,每个数字都可以出现在每个位置(在您的术语中为pow)。您可以通过添加一个额外的循环轻松地修复您的代码:

    for(int i=0;i<=x;i++)
      for(int j=0;j<=y;j++)
        for(int k=0;k<=z;k++){
    
           int t = i+j+k;
    
           for (int p=0; p<t; p++){               // added loop
             long ways = fact[t-1];
             long pow = (long) Math.pow(10,p);   // changed
    

    或者,更好的是,感谢 Niklas B. 的评论:不要添加循环,只需将 pow 分配给

    pow = (long) Math.pow(10,t - 1) / 9
    

    【讨论】:

    • 不错,我没看到
    • 当然你也可以设置pow = (10^t - 1) / 9而不使用循环
    【解决方案4】:

    编辑:我意识到链接的帖子描述了相同的内容。我误以为它与几天前漂浮在 SO 上的类似问题有关,该问题的解决方式完全不同。因此将其删除,但后来又取消删除,因为它可以向 OP 解释代码中的错误。

    这可以作为一个复杂度为 O(xyz) 的组合问题来解决。

    让我们把问题分成两部分:

    A 部分: 找出恰好由 x 4s、y 5s 和 z 6s 组成的数字之和。这很简单:

    1. 让数字如下:_ _ _..._ 4 _ ... _,其中显示的 4 出现在 10^k 位置。其余数字可以以(x+y+z-1)! / ((x-1)! * y! * z!) 方式排列。因此,4 在这个位置上贡献的总和是4 * 10^k * (x+y+z-1)! / ((x-1)! * y! * z!),即4 * x * 10^k * (x+y+z-1)! / (x! * y! * z!)

    2. 同样,5 和 6 贡献,该位置数字的总贡献为: 10^k * (x+y+z-1)! / (x! * y! * z!) * (4x + 5y + 6z).

    (例如,x=y=z=1 在 10^2 的位置,贡献是400*2 + 500*2 + 600*2 = 3000(根据示例)。根据计算,它是100 * 2! / (1! * 1! * 1!) * (4+5+6) = 3000。)

    1. 因此 (x+y+z) 位数字的总体贡献是:

    (x+y+z-1)! / (x! * y! * z!) * (4x + 5y + 6z) * (10^0 + 10^1 + ... + 10^(x+y+z-1))

    = (x+y+z-1)! / (x! * y! * z!) * (4x + 5y + 6z) * (10^(x+y+z) - 1) / 9

    所以在上面的例子中,所有 3 位数字的总和应该是: 2! / (1! * 1! * 1!) * (4+5+6) * (999)/9 = 3330。 根据示例,它是:456+465+546+564+645+654 = 3330

    B 部分:

    1. 执行与上述相同的操作,但 x y 和 z 分别采用 0-x、0-y 和 0-z 的值。这可以通过 (0..x)、(0..y)、(0..z) 端点中的 3 路嵌套循环来完成。在每次迭代中使用上述公式

    2. 所以对于上面的示例,我们有 x:0-1、y:0-1、z:0-1。可能的索引是{(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)}。根据上述公式对两位数求和,例如:

      (0, 1, 1): 1!/(0!* 1!* 1!) * (5+6) * 99/9 = 121 (1, 0, 1): 1!/(1!* 0!* 1!) * (4+6) * 99/9 = 110 (1, 1, 0): 1!/(1!* 1!* 0!) * (4+5) * 99/9 = 99

    加起来是330。 在示例中,45+54+56+65+46+64 = 330

    对于给出 15 的单位也是如此。因此总和为 15+330+3330=3675

    注意:

    1. 以上可以推广到链接问题和任意位数(不要求数字是连续的)。如果数字中有零,则必须稍微调整方法,但基本原理是相同的。

    2. 您可以使用类似的技术来计算从 1 到 100 万等中出现的 7 的数量。这是一种强大的组合方法。

    【讨论】:

      【解决方案5】:

      这就是你需要的!! 希望它能正常工作:)

      使用命名空间标准;

      typedef long long ll;

      const ll mod = 1000000007;

      int main() {

      int  q, a=0, b=0, c=0, x, y, z,  l, r,count=0;
      long long int  sum = 0,i,n,temp;
      cin >> x >> y>>z;
      string xyz = "4";
      for (i = 0; i>-1; i++)
      {
          n = i;
          //sum = 12345620223994828225;
          //cout << sum;
          while (n > 0)
          {
              temp = n % 10;
              if
                  (temp == 4)
              {
                  a++;
              }
              if (temp == 5)
              {
                  b++;
              }
              if (temp == 6)
              {
                  c++;
              }
              count++;
              n = n / 10;
      
          }
      
          if (a <= x && b <= y && c <= z && (a + b + c) == count)
          {
              temp = i%mod;
              sum = (sum + temp) % mod;
      
          }
          else if ((a + b + c) > (x + y + z))
              break;
          if (count == c)
          {
              i = 4 * pow(10, c);
          }
          count = 0;
          a = 0;
          b = 0;
          c = 0;
          temp = 0;
      }
      cout << sum+4;
      
      return 0;
      

      }

      【讨论】:

      • 如果你编辑你的答案并正确格式化代码,我很乐意给你+1。
      【解决方案6】:

      只需计算 4,5 和 6 的出现次数,然后使用 memoization 将其存储在第二个变量中。 下面是 C++ 代码

      #include <bits/stdc++.h>
      #define ll int
      #define mod 1000000007
      using namespace std;
      struct p
      {
          ll f,s;
      }dp[102][102][102]={0};
      
      p c(ll x,ll y,ll z)
      {
          if (min(x,min(y,z)) < 0)
          {
              p temp;
              temp.f=temp.s=0;
              return temp;
          }
          if (!max(x,max(y,z)))
          {
              p temp;
              temp.f=1;
              temp.s=0;
              return temp;
          }
          if(dp[x][y][z].f&&dp[x][y][z].s) return dp[x][y][z];
          p ans;
          ans.f=ans.s=0;
          for (int i=4;i<7;i++)
          {
              p temp;
              if(i==4) temp=c(x-1, y, z);
              if(i==5) temp=c(x, y-1, z);
              if(i==6) temp=c(x, y, z-1);
              ans.f = (ans.f+temp.f)%mod;
              ans.s = ((long long)ans.s+((long long)i)*(long long)(temp.f) + 10*(long long)temp.s)%mod;
          }
          dp[x][y][z].f=ans.f;
          dp[x][y][z].s=ans.s;
        return ans;
      }
      
      int main()
      {
          ll x,y,z,ans=0;
          scanf("%d%d%d",&x,&y,&z);
          for (ll i = 0; i <= x; ++i)
          {
              for (ll j = 0; j <= y; ++j)
              {
                  for (ll k = 0; k <= z; ++k)
                  {
                     ans = (ans + c(i, j, k).s)%mod;
                     cout<<dp[i][j][k].f<<" "<<dp[i][j][k].s<<endl;
                  }
              }
          }
          printf("%d",ans);
        return 0;
      }
      

      【讨论】:

        【解决方案7】:

        python 3 中的解决方案,它使用重复算法置换。可以适应其他情况,因为输入是一个字典,其中键作为请求的数字,值是每个数字的计数。

        算法说明: 您可以将排列视为一棵树,其中根包含一个零长度的数字,它的子节点代表 1 位数字,下一级有 2 位数字等。每个节点有 3 个子节点,它们代表父节点的值扩展了一个数字。所以该算法基本上是一个前序树遍历。每个递归调用都获取当前数字和要添加的数字(保存在字典中,数字作为键,计数作为值)。它在字典上迭代,依次添加每个可能的数字,然后用新数字和剩下的数字递归。该方法还在开头返回当前数字,然后执行所述递归。

        #!/usr/bin/env python3
        
        import itertools
        import copy
        
        class Matrix:
          def __init__(self, dim):
            m=None
            for i in dim:
              m=[copy.deepcopy(m) for j in range(i)]
            self.mat=m
          def getVal(self, coord):
            m=self.mat
            for i in coord:
              m=m[i]
            return m
          def setVal(self, coord, val):
            m=self.mat
            l=coord.pop()
            for i in coord:
              m=m[i]
            coord.append(l)
            m[l]=val
        
        def sumOfNumbers(digits):
          def _sumOfNumbers(counts):
            max_v=-1
            for v in counts:
              if v<0:
                return (0,0)
              elif v>max_v:
                max_v=v
            if m.getVal(counts)==None:
              c=0
              s=0
              if max_v==0:
                c=1
              else:
                for i, d in enumerate(digits.keys()):
                  counts[i]-=1
                  r=_sumOfNumbers(counts)
                  counts[i]+=1
                  c+=r[0]
                  s+=r[1]*10+r[0]*d
        
              m.setVal(counts, (c,s))
            return m.getVal(counts)
        
          dim=[v+1 for v in digits.values()]
          m=Matrix(dim)
          tot_val=0
          for i in itertools.product(*map(lambda x: range(x), dim)):
            r=_sumOfNumbers(list(i))
            tot_val+=r[1]
        
          return tot_val
        
        def main():
          x=1
          y=1
          z=1
          print(x,y,z)
          print(sumOfNumbers({4: x, 5: y, 6: z}))
        
        if __name__ == "__main__":
          main()
        

        【讨论】:

        • 这是这里唯一的解决方案,它不会硬编码我们使用的位数。也就是说,它可以从任何一组数字及其计数中构造数字。它还使用带有缓存的动态编程,因此它只计算每个数字量组合的总和一次,因此它在 O(xyz) 时间内运行。
        • 我喜欢。您可以通过将参数转换为元组 tuple(sorted(counts.items()) 并将其直接用作字典中的键来简化记忆,而无需额外的类
        • 谢谢。为什么你建议在元组构造中使用 sorted ?
        • 因为否则订单未定义
        猜你喜欢
        • 1970-01-01
        • 2012-12-13
        • 1970-01-01
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        • 2018-07-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多