【问题标题】:Finding a brute force algorithm for the following cryptarithm / alphametic puzzle为以下密码/字母谜题寻找蛮力算法
【发布时间】:2013-04-14 09:03:34
【问题描述】:

我正在尝试用 C 编写一个程序来解决以下问题 cryptarithm:

一个 + 一个 = 两个

是素数

是一个完美的正方形

也就是说,我需要找到单词onetwosevennine的数值其中每个字母(o、n、e、t、w、s、v、i)都被赋予了一个数值,并且完整的数字也满足上述所有条件。

我正在考虑为每个单词创建一个 int 数组,然后 1)检查每个单词是否满足条件(例如,“七”的质数),然后 2)检查是否每个整数数组与其他词的值一致,发现其他词也满足各自的条件。

虽然我无法真正看到这个工作,因为我必须在每次迭代中不断地将 int 数组转换为单个 int,然后我不确定如何同时将数组中的每个元素与其他词匹配.

也许知道每个单词必须为真的 MIN 和 MAX 数值范围会很有用?

有什么想法吗?

【问题讨论】:

  • 忘记添加分配给每个单词的第一个字母的数字不能为零。
  • 每个字母都分配一个数字值,或者可以是任何数值(例如 a=281)?
  • @FreemanLambda 是的,每个字母必须是单个数字值,并且必须与其他单词一致(即“seven”中的“e”必须与“one”中的“e”相同" .

标签: c math cryptarithmetic-puzzle


【解决方案1】:

对于蛮力 (ish) 方法,我会从素数 seven 开始,然后使用 Sieve of Eratosthenes 得到所有素数,直到 99999。您可以丢弃所有答案,其中第 2 和第 4位数不一样。之后,您可以移动到正方形nine,因为其中三个数字由素数seven 确定。这应该很好地缩小了可能性,然后你可以使用@pmg 的答案来完成它:-)。

更新:以下 C# 程序似乎可以做到这一点

bool[] poss_for_seven = new bool[100000];       // this will hold the possibilities for `seven`
for (int seven = 0; seven < poss_for_seven.Length; seven++)
    poss_for_seven[seven] = (seven > 9999);     // `seven` must have 5 digits
// Sieve of Eratosthenes to make `seven` prime
for (int seven = 2; seven < poss_for_seven.Length; seven++) {
    for (int j = 2 * seven; j < poss_for_seven.Length; j += seven) {
        poss_for_seven[j] = false;
    }
}
// look through the array poss_for_seven[], considering each possibility in turn
for (int seven = 10000; seven < poss_for_seven.Length; seven++) {
    if (poss_for_seven[seven]) {
        int second_digit = ((seven / 10) % 10);
        int fourth_digit = ((seven / 1000) % 10);
        if (second_digit == fourth_digit) {
            int e = second_digit;
            int n = (seven % 10);   // NB: `n` can't be zero because otherwise `seven` wouldn't be prime
            for (int i = 0; i < 10; i++) {
                int nine = n * 1000 + i * 100 + n * 10 + e;
                int poss_sqrt = (int)Math.Floor(Math.Sqrt(nine) + 0.1); // 0.1 in case of of rounding error
                if (poss_sqrt * poss_sqrt == nine) {
                    int o = ((2 * e) % 10); // since 2 * `one` = `two`, we now know `o`
                    int one = o * 100 + n * 10 + e;
                    int two = 2 * one;
                    int t = ((two / 100) % 10);
                    int w = ((two / 10) % 10);
                    // turns out that `one`=236, `two`=472, `nine` = 3136.
                    // look for solutions where `s` != `v` with `s` and `v' different from `o`, `n`, `e`,`t`, `w` and `i`
                    int s = ((seven / 10000) % 10);
                    int v = ((seven / 100) % 10);
                    if (s != v && s != o && s != n && s != e && s != t && s != w && s != i && v != o && v != n && v != e && v != t && v != w && v != i) {
                        System.Diagnostics.Trace.WriteLine(seven + "," + nine + "," + one + "," + two);
                    }
                }
            }
        }
    }
}

似乎nine 总是等于3136,所以one = 236 和two = 472。但是,seven 有 21 种可能性。如果添加了没有两个数字可以取相同值的约束(这是上面的 C# 代码所做的),那么它就会减少到只有一种可能性(尽管我的代码中的一个错误意味着这个答案最初有 3 种可能性):

seven,nine,one,two
56963,3136,236,472

【讨论】:

  • 谢谢,我将在最后一部分与@pmg 的解决方案一起尝试。
  • 谢谢!我仔细检查并确保我完全理解您所做的,然后用 C 编写了一个版本并手动检查了 3 个解决方案(这是正确的)。您在最后添加额外的约束也是正确的,因为问题要求“将不同的数字分配给不同的字母”,我将其解释为我只能将每个数字用于一个字母。再次感谢。
  • 在第二个解决方案中,S = 7 和 W = 7。在第三个解决方案中,V = 7 和 W = 7。
  • 很好发现@FreemanLambda :-)。我的代码中有一个错误,因为我有 t = ((two / 100) % 100) 和 w = ((two / 100) % 10),而我应该有 t = ((two / 100) % 10) w = ((二 / 10) % 10)。无论如何,我现在已经对其进行了编辑和更正。
【解决方案2】:

我刚刚找到时间构建一个 c 程序来解决您的密码问题。 我认为在开始蛮力编程之前从数学上解决问题将大大提高输出速度。

一些数学(数论): 由于 ONE + ONE = TWO,O 不能大于 4,因为 ONE + ONE 会产生 4 位数字。 O也不能为0。两个以O结尾并且是偶数,因为它是2 * ONE。 将这 3 个过滤器应用于 O,可能的值仍然是 O= {2,4} 因此 E 可以是 {1,2,6,7},因为 (E+E) 模数 10 必须 = O。更具体地说,O=2 暗示 E={1,6},O=4 暗示 E={2, 7} 现在让我们过滤 N。鉴于 SEVEN 是素数,N 必须是奇数。 N 也不能是 5,因为所有以 5 结尾的都可以被 5 整除。因此 N={1,3,7,9}

现在我们已经减少了最常出现的字符(O、E、N)的可能性,我们已经准备好用我们所有的野蛮来攻击这个密码,大大减少了迭代。

这是 C 代码:

#include <stdio.h>
#include <math.h>

#define O 0
#define N 1
#define E 2
#define T 3
#define W 4
#define S 5
#define V 6
#define I 7

bool isPerfectSquare(int number);
bool isPrime(int number);
void printSolutions(int countSolutions);
int filterNoRepeat(int unfilteredCount);

int solutions[1000][8]; // solution holder
int possibilitiesO[2] = {2,4}; 
int possibilitiesN[4] = {1,3,7,9};
int possibilitiesE[4] = {1,6,2,7};

void main() {
    int countSolutions = 0;
    int numberOne;
    // iterate to fill up the solutions array by: one + one = two
    for(int o=0;o<2;o++) {
        for(int n=0;n<4;n++) {
            for(int e=2*o;e<2*o+2;e++) { // following code is iterated 2*4*2 = 16 times
                numberOne = 100*possibilitiesO[o] + 10*possibilitiesN[n] + possibilitiesE[e];
                int w = ((2*numberOne)/10)%10;
                int t = ((2*numberOne)/100)%10;
                // check if NINE is a perfect square
                for(int i=0;i<=9;i++) { // i can be anything ----- 10 iterations
                    int numberNine = 1000*possibilitiesN[n] + 100*i + 10*possibilitiesN[n] + possibilitiesE[e];
                    if(isPerfectSquare(numberNine)) {
                        // check if SEVEN is prime
                        for(int s=1;s<=9;s++) { // s cant be 0 ------ 9 iterations
                            for(int v=0;v<=9;v++) { // v  can be anything other than s ------- 10 iterations
                                if(v==s) continue;
                                int numberSeven = 10000*s + 1000*possibilitiesE[e] + 100*v + 10*possibilitiesE[e] + possibilitiesN[n];
                                if(isPrime(numberSeven)) { // store solution
                                    solutions[countSolutions][O] = possibilitiesO[o];
                                    solutions[countSolutions][N] = possibilitiesN[n];
                                    solutions[countSolutions][E] = possibilitiesE[e];
                                    solutions[countSolutions][T] = t;
                                    solutions[countSolutions][W] = w;
                                    solutions[countSolutions][S] = s;
                                    solutions[countSolutions][V] = v;
                                    solutions[countSolutions][I] = i;
                                    countSolutions++;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    // 16 * 9 * 10 * 10 = 14400 iterations in the WORST scenario, conditions introduced reduce MOST of these iterations to 1 if() line
    // iterations consumed by isPrime() function are not taken in count in the aproximation above.
    // filter solutions so that no two letter have the same digit
    countSolutions = filterNoRepeat(countSolutions);
    printSolutions(countSolutions); // voila!
}

bool isPerfectSquare(int number) { // check if given number is a perfect square
    double root = sqrt((double)number);
    if(root==floor(root)) return true;
    else return false;
}

bool isPrime(int number) { // simple algoritm to determine if given number is prime, check interval from sqrt(number) to number/2 with a step of +2
    int startValue = sqrt((double)number);
    if(startValue%2==0) startValue--; // make it odd
    for(int k=startValue;k<number/2;k+=2) {
        if(number%k==0) return false;
    }
    return true;
}

void printSolutions(int countSolutions) {
    for(int k=0;k<countSolutions;k++) {
        int one = 100*solutions[k][O] + 10*solutions[k][N] + solutions[k][E];
        int two = 100*solutions[k][T] + 10*solutions[k][W] + solutions[k][O];
        int seven = 10000*solutions[k][S] + 1000*solutions[k][E] + 100*solutions[k][V] + 10*solutions[k][E] + solutions[k][N];
        int nine = 1000*solutions[k][N] + 100*solutions[k][I] + 10*solutions[k][N] + solutions[k][E];
        printf("ONE: %d, TWO: %d, SEVEN: %d, NINE %d\n",one,two,seven,nine);
    }
}

int filterNoRepeat(int unfilteredCount) {
    int nrSol = 0;
    for(int k=0;k<unfilteredCount;k++) {
        bool isValid = true;
        for(int i=0;i<7;i++) { // if two letters match, solution is not valid
            for(int j=i+1;j<8;j++) {
                if(solutions[k][i]==solutions[k][j]) {
                    isValid = false;
                    break;
                }
            }
            if(!isValid) break;
        }
        if(isValid) { // store solution
            for(int i=0;i<8;i++) {
                solutions[nrSol][i] = solutions[k][i];
            }
            nrSol++;
        }
    }
    return nrSol;
}

如果您仍然对此感兴趣,可以自己尝试代码:P。结果是一个单一的解决方案:一:236,二:472,七:56963,九:3136 该解决方案与随机的解决方案相同,确认了我认为两种算法的正确性:)。 感谢您提供这么好的密码,祝您有美好的一天!

【讨论】:

    【解决方案3】:

    暴力破解!

    #define ONE ((o*100) + (n*10) + e)
    #define TWO ((t*100) + (w*10) + o)
    #define SEVEN ((s*10000) + (e*1010) + (v*100) + n)
    #define NINE ((n*1010) + (i*100) + e)
    
    for (o = 1; o < 10; o++) {                /* 1st digit cannot be zero (one) */
      for (n = 1; n < 10; n++) {              /* 1st digit cannot be zero (nine) */
        if (n == o) continue;
        for (e = 0; n < 10; n++) {
          if (e == n) continue;
          if (e == o) continue;
                  /* ... */
                          if (ONE + ONE == TWO) /* whatever */;
                  /* ... */
        }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-05
      • 1970-01-01
      • 1970-01-01
      • 2017-09-28
      相关资源
      最近更新 更多