将一个带有‘?’的数拆解成两个由4-7组成的数,求该数最小值 回溯法 数学 SRM 665 Div2 Hard LuckySum

本文通过分析SRM 665 Div2 Hard LuckySum问题,探讨了一种利用回溯法来找到两个由4和7组成的幸运数,使得它们相加的结果最小,并且匹配指定的数位模式。在解决过程中,通过优化减少了搜索空间,从最初的2^n降低到3^n,从而解决了大数位数导致的时间复杂性问题。文章中提供了详细的解题思路和回溯法实现代码。
摘要由CSDN通过智能技术生成
题目 http://community.topcoder.com/stat?c=problem_statement&pm=13964&rd=16514&rm=326780&cr=22918502
题解:
http://apps.topcoder.com/wiki/display/tc/SRM+665
Div2 Hard / Div1 Easy: LuckySum
Consider the straight-forward bruteforce approach: For each of the n digit positions in the two lucky numbers we pick 4 or 7, then we try if the resulting sum matches the provided pattern and return the smallest valid one. There are 2n slots each with 2 options, so we will be trying 22n=4n combinations. For n=14, this will be too large.


   474474
   447447
+ --------
   921921
There is a cool observation we can make. When in a digit position i, the two lucky digits are distinct (4 + 7 or 7 + 4), it doesn't matter what order we assign to them. The sum up there has the same result as the following:


   444444
   477477
+ --------
   921921
Since we are only interested in the final sum and not the specific lucky numbers used to find it, then we can just always make sure to use 4+7 and ignore the cases with 7+4. This means that for each digit position we will have 3 options instead of four: 4+4, 4+7, 7+7. This changes the complexity, to 3n down from 4n. 3n is small enough.


That finishes the theoretic part of how to solve the problem. In practice, however, there is one more complications we'll meet: The two lucky numbers won't necessarily have exactly n digits. Here are some variations:


    4
    7
+ ----
   11




    444
      7
+ ------
    451     
This is the same as if we added leading zeros to the numbers. So now we have to pick 4, 7 or 0 for each digit slot:


   04
   07
+ ----
   11




    444
    007
+ ------
    451     
Does this mean that we have a larger complexity than 3n ? In reality it won't, because once we add a zero, all the digits we pick to the left of that zero digit must be 0 as well. The other lucky number might still have two options: 4 or 7. So imagine this: We initially have n digit positions, three of the options make it so there are O(3n−1) remaining options, two other options will make it so there are O(2n−1) remaining options so we have something like O(3⋅3n−1+2⋅2n−1)≡O(3n)


We also need to be careful in implementation. Some examples: If we choose to do the addition as we pick the digits, we need to simulate things like the carry. Make sure 0 is not considered a lucky number. The last digit position might have two 0s but only if there is carry. Etc. The following solution uses backtracking:


const long INF = 12345678901234567LL;
long result;
vector<int> note, A, B, C;
 
// p :current digit position
// x: current value of the number we are building
// ten_p: 10^p
void backtrack(int p, long x, long ten_p)
{
    if (p == note.size()) {
        if (x < result) {
            result = x;
        }
    } else {
        for (int j : {0,4,7} ) { //Pick B[p]
            // If B[p-1] was 0, we cannot use a non-zero one anymore
            if ( (j != 0) && (p > 0) && (B[p-1] == 0) ) {
                continue;
            }
            // If p == 0, B[p] shouldn't be zero, because '0' is not a magic number
            if ( (j == 0) && (p == 0) ) {
                continue;
            }
            for (int i : {7,4,0} ) { //Pick values for A[p]
                // Ignoring cases where i < j , optimizes the search to O(3^n)
                if (i < j) {
                    continue;
                }
                A[p] = i;
                B[p] = j;
                // Current carry (depends on p-1 )
                int c = ( ( (p!=0) && (A[p-1] + B[p-1] >= 10) ) ? 1 : 0 );
                if ( (i == 0) && (j == 0) && ( (p != C.size() -1) || (c != 1) ) ) {
                    // A case where both digits are zero is valid only when
                    // the previous step had carry and this is the last step.
                    continue;
                }
                C[p] = (c + i + j) % 10;
                // if there's carry this shouldn't be the last step
                if ( (p == C.size() - 1) && (c + i + j >= 10) ) {
                    continue;
                }
                // things should match with the note
                if ( (note[p] != -1) && (note[p] != C[p]) ) {
                    continue;
                }
                // move on to the next step
                backtrack(p+1, x + C[p] * ten_p, ten_p * 10);
            }
        }
    }
}
 
long construct(string note)
{
    int n = note.size();
    this->note.resize(n);
    A.resize(n);
    B.resize(n);
    C.resize(n);
    for (int i = 0; i < n; i++) {
        this->note[n-i-1] = ( (note[i] == '?') ? -1 : (note[i] - '0') );
    }
    result = INF;
    backtrack(0, 0LL, 1);
    return (result >= INF) ? -1: result; 
}




附上tourist代码
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
 
using namespace std;
 
class LuckySum {
public:
  long long construct(string);
};
 
int len, a[4242];
long long ans;
 
void dfs(int pos, int alive, int carry, long long num, long long step);
 
void test_add(int pos, int alive, int carry, long long num, long long step, int add) {
  int digit = add + carry;
  int new_carry = 0;
  if (digit >= 10) {
    digit -= 10;
    new_carry = 1;
  }
  if (pos == 0 && digit == 0) {
    return;
  }
  if (a[pos] != -1 && a[pos] != digit) {
    return;
  }
  dfs(pos - 1, alive, new_carry, num + step * digit, step * 10);
}
 
void dfs(int pos, int alive, int carry, long long num, long long step) {
  if (pos == -1) {
    if (carry == 1) {
      return;
    }
    if (ans == -1 || num < ans) {
      ans = num;
    }
    return;
  }
  if (alive == 0) {
    test_add(pos, alive, carry, num, step, 0);
    return;
  }
  if (pos != len - 1) {
    dfs(pos, alive - 1, carry, num, step);
  }
  if (alive == 1) {
    test_add(pos, alive, carry, num, step, 4);
    test_add(pos, alive, carry, num, step, 7);
  } else {
    test_add(pos, alive, carry, num, step, 8);
    test_add(pos, alive, carry, num, step, 11);
    test_add(pos, alive, carry, num, step, 14);
  }
}
 
long long LuckySum::construct(string note) {
  len = note.length();
  for (int i = 0; i < len; i++) {
    if (note[i] == '?') {
      a[i] = -1;
    } else {
      a[i] = note[i] - '0';
    }
  }
  ans = -1;
  dfs(len - 1, 2, 0, 0, 1);
  return ans;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值