从匹配棒中制作数字的算法

时间:2011-07-21 12:49:43

标签: c++ algorithm recursion puzzle

我制作了一个程序来解决ACM中的this problem问题。

  

火柴棍是表示数字的理想工具。使用火柴棍表示十位小数的常用方法如下:

     

这与普通闹钟上的数字显示方式相同。使用给定数量的火柴棍,您可以生成多种数字。我们想知道使用所有火柴棍可以创建的最小和最大数字是什么。

     

输入

     

在第一行上有一个正数:测试用例的数量,最多为100.每个测试用例之后:

     

一行整数n(2≤n≤100):你有的火柴数。   输出

     

每个测试用例:

     

您可以创建的最小和最大数字的一行,由单个空格分隔。两个数字都应该是正数并且不包含前导零。   样本输入

     

4   3   6   7   15   样本输出

     

7 7   6 111   8 711   108 7111111

问题在于,对于100个火柴棍而言,解决它的速度太慢了。搜索树太大了,无法强制它。

以下是前10个的结果:

2:1 1

3:7 7

4:4 11

5:2 71

6:6 111

7:8 711

8:10 1111

9:18 7111

10:22 11111

最大值的模式很简单,但我没有看到最小值的快捷方式。有人可以建议一个更好的方法来解决这个问题吗?这是我使用的代码:

    #include <iostream>
    #include <string>
    using namespace std;

    #define MAX 20 //should be 100

    //match[i] contains number of matches needed to form i
    int match[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
    string mi[MAX+1], ma[MAX+1];
    char curr[MAX+1] = "";

    //compare numbers saved as strings
    int mycmp(string s1, string s2)
    {
        int n = (int)s1.length();
        int m = (int)s2.length();
        if (n != m)
            return n - m;
        else
            return s1.compare(s2);
    }

    //i is the current digit, used are the number of matchsticks so far
    void fill(int i, int used)
    {
        //check for smaller and bigger values
        if (mycmp(curr, mi[used]) < 0) mi[used] = curr;
        if (mycmp(curr, ma[used]) > 0) ma[used] = curr;

        //recurse further, don't start numbers with a zero
        for (int a = i ? '0' : '1'; a <= '9'; a++) {
            int next = used + match[a-'0'];
            if (next <= MAX) {
                curr[i] = a;
                curr[i+1] = '\0';
                fill(i + 1, next);
            }
        }
    }

    int main()
    {
        //initialise 
        for (int i = 0; i <= MAX; i++) {
            mi[i] = string(MAX, '9');
            ma[i] = "0";
        }

        //precalculate the values
        fill(0, 0);

        int n;
        cin >> n;

        //print those that were asked
        while (n--) {
            int num;
            cin >> num;
            cout << mi[num] << " " << ma[num] << endl;
        }

        return 0;
    }

编辑:我最终使用了动态编程解决方案。我之前用dp尝试了它,但我正在弄乱二维状态数组。这里的解决方案要好得多。谢谢!

6 个答案:

答案 0 :(得分:4)

您可以使用动态编程解决方案。

假设您有n个匹配项,并且您知道如何解决所有n-k个匹配项的问题(最小数量),其中k获取与匹配数相对应的所有值每个数字使用(2为1,5为3,等等)

然后递归地导出解决方案。假设你用一个(在最不重要的位置)结束你的号码,那么通过写(best solution with n-2 matches) 1获得最佳解决方案。假设你用两个结束它,最好的解决方案是(best solution with n-5 matches) 2。等等 ;最终你可以比较这十个数字,并选出最好的数字。

现在,您所要做的就是为所有n设置比输入更小的最佳解决方案,递归。

编辑:如果以直接的方式实现此算法,最终会出现指数复杂性。这里的诀窍是要注意,如果你的核心函数MinimumGivenNMatches只有一个参数n。因此,你最终会用相同的值多次调用它。

为了使复杂性变得线性,您只需要使用辅助数组为每个n记忆(即记住)解决方案。

答案 1 :(得分:2)

为了找到结果:

  • 首先找到最小数字的最小位数
  • 然后从最重要的数字前进到最不重要的数字。

应选择每个数字,以便存在剩余数字的解决方案。 每个数字需要2到7个匹配。所以你必须选择最小的第N个“顶部”数字,留下2 *(N-1)和7 *(N-1)之间的剩余匹配数。

不要忘记在搜索结果的最高位数时必须排除0。

作为旁注,使该算法有效的一个原因是每个(匹配)值在2和7之间至少有一个相应的数字。

编辑: 10场比赛的示例 10场比赛 - &gt; 2位数 顶部数字的可接受匹配数量= 3到7之间 最小的数字,需要3到7个匹配 - &gt; 2(需要5场比赛),0被排除在外 选择第一个数字= 2

剩余5场比赛 - &gt;
第二个(在这种情况下是最后一个)数字的可接受的匹配数量=正好5个 最小的数字,需要5个匹配 - &gt; 2
选择第二个数字= 2

结果= 22。

编辑此问题的代码

#include <iostream>
#include <vector>

std::vector<int> nbMatchesForDigit;

long long minNumberForMatches(unsigned int nbMatches)
{
    int nbMaxMatchesForOneDigit = 7;
    int nbMinMatchesForOneDigit = 2;
    int remainingMatches = nbMatches;
    int nbDigits = 1 + nbMatches / nbMaxMatchesForOneDigit; 
    long long result = 0;
    for (int idDigit = 0 ; idDigit < nbDigits ; ++idDigit )
    {
        int minMatchesToUse = std::max(nbMinMatchesForOneDigit, remainingMatches - nbMaxMatchesForOneDigit * (nbDigits - 1 - idDigit));
        int maxMatchesToUse = std::min(nbMaxMatchesForOneDigit, remainingMatches - nbMinMatchesForOneDigit * (nbDigits - 1 - idDigit));
        for (int digit = idDigit > 0 ? 0 : 1 ; digit <= 9 ; ++digit )
        {
            if( nbMatchesForDigit[digit] >= minMatchesToUse && 
                nbMatchesForDigit[digit] <= maxMatchesToUse )
            {
                result = result * 10 + digit;
                remainingMatches -= nbMatchesForDigit[digit];
                break;
            }
        }
    }
    return result;
}

int main()
{
    nbMatchesForDigit.push_back(6);
    nbMatchesForDigit.push_back(2);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(4);
    nbMatchesForDigit.push_back(5);
    nbMatchesForDigit.push_back(6);
    nbMatchesForDigit.push_back(3);
    nbMatchesForDigit.push_back(7);
    nbMatchesForDigit.push_back(6);

    for( int i = 2 ; i <= 100 ; ++i )
    {
        std::cout << i << " " << minNumberForMatches(i) << std::endl;
    }
}

答案 2 :(得分:2)

使用dynamic programming代替递归。存储计算值并重新使用它们要快得多。实际上,它将指数运行时间转换为多项式运算时间。

基本思想是拥有一个数组min,它可以跟踪使用n个火柴棍完成的最小数量。所以

min[0] = ""
min[1] = infinity
min[2] = "1"
min[3] = min("1+"min[3-2],"7")
min[4] = min("1"+min[4-2],"7"+min[4-3])
etc

答案 3 :(得分:1)

对于最小值,请注意,由于不允许前导零,因此您希望最小化位数。最小位数为ceil(n/7)

然后很容易计算必须在前导数字中使用的最小数量的火柴棍,从而得到前导数字的最小可能值。

答案 4 :(得分:0)

我能够用int nofDigits = n/7+ ((0 == n%7) ? 0 : 1)解决问题,其中d是位数。我们的想法是,首先我们计算所需最小数字的最小位数。这可以通过n计算,其中nofDigits是火柴棍的数量。现在创建一个void minNumWithNMatches_no_dp (int n) { if (n < 2) return ; int digits[] = {-1, -1, 1, 7, 4, 2, 0, 8}; int nofDigits = n/7+ ((0 == n%7) ? 0 : 1); int *matchesArr = new int [nofDigits]; int tmp = n; for (int i = 0; i < nofDigits; ++i) { matchesArr[i] = tmp/7 ? 7 : tmp%7; tmp -= matchesArr[i]; } if (nofDigits > 1) { switch (matchesArr[nofDigits - 1]) { case 1: case 4: { --matchesArr[nofDigits - 2]; ++matchesArr[nofDigits - 1]; break; } case 3: { if (nofDigits > 2) { --matchesArr[nofDigits - 2]; --matchesArr[nofDigits - 3]; } else { matchesArr[nofDigits - 2] -= 2; } matchesArr[nofDigits - 1] += 2; break; } case 2: case 5: case 6: case 7: default: { ; } } } for (int i = nofDigits - 1; i >= 0; --i) { int digit = digits[matchesArr[i]]; if ((i == nofDigits - 1) && (digit == 0)) digit = 6; std::cout<< digit; } } 数组。现在我们开始从最低有效数字到最大有效数字(MSD)之前的一个数字填充最大可能的火柴(7),最后将所有剩余火柴杆分配到最高有效数字(MSD)。现在有三种改进的可能性,具体取决于MSD的火柴杆数量:

如果MSD的火柴数为1,那么我们可以通过从其相邻数字借一个火柴棍来使其成为2。这将使相邻数字6的火柴杆数量相当于0

2d如果MSD的火柴数为4,那么也与之前的情况相同,我们可以将MSD的火柴数增加到5,相当于2

如果MSD的火柴数是3,那么我们必须看看总数是否大于2然后我们可以从MSD的两个相邻数字递减1,否则我们将减少一个相邻数字两次并增加MSD的火柴数量为2

最后,遍历数组并用相应的数字替换火柴棍的数量。

完整的计划:

function isPushSupported() {
    var isSupported = false;
    if (WL.Client.Push){
        isSupported = WL.Client.Push.isPushSupported();
    }
    WL.SimpleDialog.show("Tag Notifications", JSON.stringify(isSupported), [
     {
        text : 'Close',
        handler : function() {

        }
     },{
        text : 'OK',
        handler : function(){
        if (WL.Client.Push) {
            WL.Client.Push.onReadyToSubscribe = function() {
                WL.SimpleDialog.show("Tag Notifications", "Ready to subscribe", [
                    {
                        text : 'Close',
                        handler : function() {}
                 },{
                      text : 'ok',
                      handler : function() {
                      subscribeToSampleTag1();
                      subscribeToSampleTag2();
                      isSubscribedToTags();
                 }
                 }
                 ]);
                    $('.subscribeToTagButton').removeAttr('disabled');
                    $('.unsubscribeFromTagButton').removeAttr('disabled');
                };
      }
        }
       }
      ]);
}
    function subscribeToSampleTag1() {
      WL.Client.Push.subscribeTag("tag1", {
      onSuccess: subscribeTagSuccess,
      onFailure: subscribeTagFailure
    });
    }
    function subscribeToSampleTag2() {
      WL.Client.Push.subscribeTag("tag2", {
      onSuccess: subscribeTagSuccess,
      onFailure: subscribeTagFailure
      });
     }
    function isSubscribedToTags() {
      var subscribedTagsSample1 = WL.Client.Push.isTagSubscribed("tag1");
      var subscribedTagsSample2 = WL.Client.Push.isTagSubscribed("tag2");
      WL.SimpleDialog.show("Tag Notifications", 'sample-tag1: ' +          subscribedTagsSample1 + '\n' + 'sample-tag2: ' + subscribedTagsSample2, [ {
      text : 'Close',
      handler : function() {}
        }
       ]);
       }
     WL.Client.Push.onMessage = function (props, payload) {
        WL.SimpleDialog.show("Tag Notifications", "Provider notification data: " + JSON.stringify(props), [ {
           text : 'Close',
            handler : function() {
        WL.SimpleDialog.show("Tag Notifications", "Application notification data: " + JSON.stringify(payload), [ {
            text : 'Close',
            handler : function() {}
          }]);
    }
}]);

答案 5 :(得分:0)

#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#include <map>
using namespace std;

int main()
{
//  freopen("22.txt", "r", stdin);
    vector<char> count(10);
    map<int, char> Min_1;
    Min_1[0] = '8';
    Min_1[2] = '1';
    Min_1[3] = '7';
    Min_1[4] = '4';
    Min_1[5] = '2';
    count[0] = '6';
    count[1] = '2';
    count[2] = '5';
    count[3] = '5';
    count[4] = '4';
    count[5] = '5';
    count[6] = '6';
    count[7] = '3';
    count[8] = '7';
    count[9] = '6';
    int N = 99, P = 2;
    cin >> N;
    while(N --)
    {
        cin >> P;
        vector<char> min, max;
        int a = P, b = P;
        int total = (a + 6) / 7;
        int left = a % 7;
        bool first = true;
        char lastInsert = 0;
        while(a != 0)
        {
            if(total == 1)
            {
                if(left != 6)
                {
                    min.push_back(Min_1[left]);
                }else if(left == 6)
                {
                    if(!first)
                        min.push_back('0');
                    else
                        min.push_back('6');
                }
                break;
            }
            if(left == 0)
            {
                min.insert(min.end(), total, '8');
                break;
            }else{
                if(first && left <= 2)
                {
                    min.push_back('1');
                    lastInsert = 1;
                }else if(first && left < 6)
                {
                    min.push_back('2');
                    lastInsert = 2;
                }else if(first && left == 6)
                {
                    min.push_back('6');
                    lastInsert = 6;
                }else if(!first)
                {
                    min.push_back('0');
                    lastInsert = 0;
                } 
            }
            int temp = count[lastInsert] - '0';
            a -= temp;
            left = a % 7;
            total = (a + 6) / 7;
            first = false;
        }

        if(b % 2 == 1)
        {
            max.push_back('7');
            max.insert(max.end(), (b - 3) / 2, '1');
        }else{
            max.insert(max.end(), b / 2, '1');
        }
        string res_min = string(min.begin(), min.end());
        string res_max = string(max.begin(), max.end());
        cout << res_min << " " << res_max << endl;
        P ++;
    }
    return 0;
}

这是我的答案,希望有所帮助