从火柴棍生成数字的算法

Algorithm to make numbers from match sticks

本文关键字:数字 算法 火柴      更新时间:2023-10-16

我从ACM上编写了一个程序来解决这个问题。

火柴棒是表示数字的理想工具。用火柴棍表示10位十进制数字的常用方法如下:

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

输入

第一行一个正数:测试用例的数量,最多100个。之后每个测试用例:

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

每testcase:

一行,用一个空格分隔最小和最大的数字。两个数字都应该是正数,并且不包含前导零。示例输入

43.6715样例输出

7 7111年6711年8108 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] = '';
                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;
    }

EDIT:我最终使用了动态规划解决方案。我之前用dp试过,但是我用的是二维状态数组。这里的解决方案要好得多。谢谢!

您可以使用动态规划解决方案。

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

然后递归地推导解。假设你的数字以1结束(在最低有效位),那么最好的解决方案是写(best solution with n-2 matches) 1。假设以2结尾,最好的解是(best solution with n-5 matches) 2。等等;最后你可以比较这十个数字,选出最好的一个。

那么现在,你所要做的就是为所有小于你输入的n设计最佳解决方案,递归地。

EDIT:如果你以一种直接的方式实现这个算法,你最终会得到一个指数级的复杂性。这里的技巧是要注意,如果您的核心函数MinimumGivenNMatches只接受一个参数n。因此,你最终会用相同的值调用它很多次。

要使复杂度线性化,你只需要记住(即。记住)每个n的解决方案,使用一个辅助数组。

为了查找结果:

  • 首先找到最小数字的最小位数
  • 则从最高有效位到最低有效位。

每个数字都应该被选中,以便对剩余的数字存在一个解。每个数字需要2到7次匹配。因此,您必须选择最小的第n个"顶部"数字,使剩余匹配数在2*(N-1)和7*(N-1)之间。

不要忘记,在搜索结果的最高有效数字时,必须将0排除在外。

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

EDIT:示例为10个匹配10个匹配-> 2位
最高数字的可接受匹配次数=在3到7之间。
需要匹配3到7次的最小数字-> 2(需要匹配5次),0被排除。
第一个数字= 2

剩余5场比赛-->
第二个(在本例中是最后一个)数字的可接受匹配数= 5
需要5次匹配的最小数字-> 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;
    }
}

使用动态规划代替递归。存储计算值并重用它们的速度要快得多。事实上,它把指数型的运行时间变成了多项式型的。

基本思想是有一个数组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

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

那么就很容易计算出必须在前导数字上使用的最小火柴棍数,从中你可以得到前导数字的最小可能值。

我能够解决O(d)的问题,其中d是位数。这个想法是,首先我们计算最小位数的最小位数。这可以通过int nofDigits = n/7+ ((0 == n%7) ? 0 : 1)来计算,其中n是火柴棒的数量。现在创建一个nofDigits数组。现在我们开始填充最大可能的火柴棒(7),从最低有效数字到最高有效数字(MSD)之前的一位,最后将所有剩余的火柴棒分配给最高有效数字(MSD)。现在有3种改进的可能性,这取决于MSD的火柴棒数量:

1如果MSD的火柴棍数是1,那么我们可以从相邻的数字上借一根火柴棍使它成为2。这将使相邻数字6对应的火柴棍数等于0

2d如果MSD的火柴棒数量是4,那么同样和之前的情况一样,我们可以将MSD的火柴棒数量增加到5,这相当于2

第3条如果MSD的火柴棍数为3,那么我们必须看看总位数是否大于2那么我们可以从MSD的两个相邻数字中减去1或者我们将相邻数字减去两次并将MSD的火柴棍数增加2

最后通过遍历数组并用对应的数字替换火柴棍的数目。

完整的程序:
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;
    }
}
#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;
}

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