How to find all possible values of four variables when squared sum to N?

后端 未结 4 397
醉酒成梦
醉酒成梦 2020-12-28 23:39

A^2+B^2+C^2+D^2 = N Given an integer N, print out all possible combinations of integer values of ABCD which solve the equation.

<
相关标签:
4条回答
  • 2020-12-29 00:13

    Naive brute force would be something like:

    n = 3200724;
    lim = sqrt (n) + 1;
    for (a = 0; a <= lim; a++)
        for (b = 0; b <= lim; b++)
            for (c = 0; c <= lim; c++)
                for (d = 0; d <= lim; d++)
                    if (a * a + b * b + c * c + d * d == n)
                        printf ("%d %d %d %d\n", a, b, c, d);
    

    Unfortunately, this will result in over a trillion loops, not overly efficient.

    You can actually do substantially better than that by discounting huge numbers of impossibilities at each level, with something like:

    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        int n = atoi (argv[1]);
        int a, b, c, d, na, nb, nc, nd;
        int count = 0;
    
        for (a = 0, na = n; a * a <= na; a++) {
            for (b = 0, nb = na - a * a; b * b <= nb; b++) {
                for (c = 0, nc = nb - b * b; c * c <= nc; c++) {
                    for (d = 0, nd = nc - c * c; d * d <= nd; d++) {
                        if (d * d == nd) {
                            printf ("%d %d %d %d\n", a, b, c, d);
                            count++;
                        }
                        tot++;
                    }
                }
            }
        }
    
        printf ("Found %d solutions\n", count);
    
        return 0;
    }
    

    It's still brute force, but not quite as brutish inasmuch as it understands when to stop each level of looping as early as possible.

    On my (relatively) modest box, that takes under a second (a) to get all solutions for numbers up to 50,000. Beyond that, it starts taking more time:

         n          time taken
    ----------      ----------
       100,000            3.7s
     1,000,000       6m, 18.7s
    

    For n = ten million, it had been going about an hour and a half before I killed it.

    So, I would say brute force is perfectly acceptable up to a point. Beyond that, more mathematical solutions would be needed.


    For even more efficiency, you could only check those solutions where d >= c >= b >= a. That's because you could then build up all the solutions from those combinations into permutations (with potential duplicate removal where the values of two or more of a, b, c, or d are identical).

    In addition, the body of the d loop doesn't need to check every value of d, just the last possible one.

    Getting the results for 1,000,000 in that case takes under ten seconds rather than over six minutes:

       0    0    0 1000
       0    0  280  960
       0    0  352  936
       0    0  600  800
       0   24  640  768
       :    :    :    :
     424  512  512  544
     428  460  500  596
     432  440  480  624
     436  476  532  548
     444  468  468  604
     448  464  520  560
     452  452  476  604
     452  484  484  572
     500  500  500  500
    Found 1302 solutions
    
    real   0m9.517s
    user   0m9.505s
    sys    0m0.012s
    

    That code follows:

    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        int n = atoi (argv[1]);
        int a, b, c, d, na, nb, nc, nd;
        int count = 0;
    
        for (a = 0, na = n; a * a <= na; a++) {
            for (b = a, nb = na - a * a; b * b <= nb; b++) {
                for (c = b, nc = nb - b * b; c * c <= nc; c++) {
                    for (d = c, nd = nc - c * c; d * d < nd; d++);
                    if (d * d == nd) {
                        printf ("%4d %4d %4d %4d\n", a, b, c, d);
                        count++;
                    }
                }
            }
        }
    
        printf ("Found %d solutions\n", count);
    
        return 0;
    }
    

    And, as per a suggestion by DSM, the d loop can disappear altogether (since there's only one possible value of d (discounting negative numbers) and it can be calculated), which brings the one million case down to two seconds for me, and the ten million case to a far more manageable 68 seconds.

    That version is as follows:

    #include <stdio.h>
    #include <math.h>
    
    int main(int argc, char *argv[]) {
        int n = atoi (argv[1]);
        int a, b, c, d, na, nb, nc, nd;
        int count = 0;
    
        for (a = 0, na = n; a * a <= na; a++) {
            for (b = a, nb = na - a * a; b * b <= nb; b++) {
                for (c = b, nc = nb - b * b; c * c <= nc; c++) {
                    nd = nc - c * c;
                    d = sqrt (nd);
                    if (d * d == nd) {
                        printf ("%d %d %d %d\n", a, b, c, d);
                        count++;
                    }
                }
            }
        }
    
        printf ("Found %d solutions\n", count);
    
        return 0;
    }
    

    (a): All timings are done with the inner printf commented out so that I/O doesn't skew the figures.

    0 讨论(0)
  • 2020-12-29 00:15

    nebffa has a great answer. one suggestion:

     step 3a:  max_b = min(a, floor(square_root(n - a^2))) // since b <= a
    

    max_c and max_d can be improved in the same way too.

    Here is another try:

    1. generate array S: {0, 1, 2^2, 3^2,.... nr^2} where nr = floor(square_root(N)). 
    

    now the problem is to find 4 numbers from the array that sum(a, b,c,d) = N;

    2. according to neffa's post (step 1a & 1b), a (which is the largest among all 4 numbers) is between [nr/2 .. nr]. 
    

    We can loop a from nr down to nr/2 and calculate r = N - S[a]; now the question is to find 3 numbers from S the sum(b,c,d) = r = N -S[a];

    here is code:

    nr = square_root(N);
    S = {0, 1, 2^2, 3^2, 4^2,.... nr^2};
    for (a = nr; a >= nr/2; a--)
    {
       r = N - S[a];
       // it is now a 3SUM problem
       for(b = a; b >= 0; b--)
       {
          r1 = r - S[b];
          if (r1 < 0) 
              continue;
    
          if (r1 > N/2) // because (a^2 + b^2) >= (c^2 + d^2)
              break;
    
          for (c = 0, d = b; c <= d;)
          {
              sum = S[c] + S[d];
              if (sum == r1) 
              {
                   print a, b, c, d;
                   c++; d--;
              }
              else if (sum < r1)
                   c++;
              else
                   d--;
          }
       }
    }
    

    runtime is O(sqare_root(N)^3).

    Here is the test result running java on my VM (time in milliseconds, result# is total num of valid combination, time 1 with printout, time2 without printout):

    N            result#  time1     time2
    -----------  -------- --------  -----------
      1,000,000   1302       859       281
     10,000,000   6262     16109      7938
    100,000,000  30912    442469    344359
    
    0 讨论(0)
  • 2020-12-29 00:24

    The Wikipedia page has some interesting background information, but Lagrange's four-square theorem (or, more correctly, Bachet's Theorem - Lagrange only proved it) doesn't really go into detail on how to find said squares.

    As I said in my comment, the solution is going to be nontrivial. This paper discusses the solvability of four-square sums. The paper alleges that:

    There is no convenient algorithm (beyond the simple one mentioned in the second paragraph of this paper) for finding additional solutions that are indicated by the calculation of representations, but perhaps this will streamline the search by giving an idea of what kinds of solutions do and do not exist.

    There are a few other interesting facts related to this topic. There exist other theorems that state that every integer can be written as a sum of four particular multiples of squares. For example, every integer can be written as N = a^2 + 2b^2 + 4c^2 + 14d^2. There are 54 cases like this that are true for all integers, and Ramanujan provided the complete list in the year 1917.

    For more information, see Modular Forms. This is not easy to understand unless you have some background in number theory. If you could generalize Ramanujan's 54 forms, you may have an easier time with this. With that said, in the first paper I cite, there is a small snippet which discusses an algorithm that may find every solution (even though I find it a bit hard to follow):

    For example, it was reported in 1911 that the calculator Gottfried Ruckle was asked to reduce N = 15663 as a sum of four squares. He produced a solution of 125^2 + 6^2 + 1^2 + 1^2 in 8 seconds, followed immediately by 125^2 + 5^2 + 3^2 + 2^2. A more difficult problem (reflected by a first term that is farther from the original number, with correspondingly larger later terms) took 56 seconds: 11399 = 105^2 + 15^2 + 8^2 + 5^2. In general, the strategy is to begin by setting the first term to be the largest square below N and try to represent the smaller remainder as a sum of three squares. Then the first term is set to the next largest square below N, and so forth. Over time a lightning calculator would become familiar with expressing small numbers as sums of squares, which would speed up the process.

    (Emphasis mine.)

    The algorithm is described as being recursive, but it could easily be implemented iteratively.

    0 讨论(0)
  • 2020-12-29 00:32

    It seems as though all integers can be made by such a combination:

    0 = 0^2 + 0^2 + 0^2 + 0^2
    1 = 1^2 + 0^2 + 0^2 + 0^2
    2 = 1^2 + 1^2 + 0^2 + 0^2
    3 = 1^2 + 1^2 + 1^2 + 0^2
    4 = 2^2 + 0^2 + 0^2 + 0^2, 1^2 + 1^2 + 1^2 + 1^2 + 1^2
    5 = 2^2 + 1^2 + 0^2 + 0^2
    6 = 2^2 + 1^2 + 1^2 + 0^2
    7 = 2^2 + 1^2 + 1^2 + 1^2
    8 = 2^2 + 2^2 + 0^2 + 0^2
    9 = 3^2 + 0^2 + 0^2 + 0^2, 2^2 + 2^2 + 1^2 + 0^2
    10 = 3^2 + 1^2 + 0^2 + 0^2, 2^2 + 2^2 + 1^2 + 1^2
    11 = 3^2 + 1^2 + 1^2 + 0^2
    12 = 3^2 + 1^2 + 1^2 + 1^2, 2^2 + 2^2 + 2^2 + 0^2
    .
    .
    .
    

    and so forth

    As I did some initial working in my head, I thought that it would be only the perfect squares that had more than 1 possible solution. However after listing them out it seems to me there is no obvious order to them. However, I thought of an algorithm I think is most appropriate for this situation:

    The important thing is to use a 4-tuple (a, b, c, d). In any given 4-tuple which is a solution to a^2 + b^2 + c^2 + d^2 = n, we will set ourselves a constraint that a is always the largest of the 4, b is next, and so on and so forth like:

    a >= b >= c >= d
    

    Also note that a^2 cannot be less than n/4, otherwise the sum of the squares will have to be less than n.

    Then the algorithm is:

    1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
    1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
    2. For a in a range (min_a, max_a)
    

    At this point we have selected a particular a, and are now looking at bridging the gap from a^2 to n - i.e. (n - a^2)

    3. Repeat steps 1a through 2 to select a value of b. This time instead of finding
    floor(square_root(n)) we find floor(square_root(n - a^2))
    

    and so on and so forth. So the entire algorithm would look something like:

    1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
    1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
    2. For a in a range (min_a, max_a)
    3a. Obtain floor(square_root(n - a^2))
    3b. Obtain the first value of b such that b^2 >= (n - a^2)/3
    4. For b in a range (min_b, max_b)
    5a. Obtain floor(square_root(n - a^2 - b^2))
    5b. Obtain the first value of b such that b^2 >= (n - a^2 - b^2)/2
    6. For c in a range (min_c, max_c)
    7. We now look at (n - a^2 - b^2 - c^2). If its square root is an integer, this is d.
    Otherwise, this tuple will not form a solution
    

    At steps 3b and 5b I use (n - a^2)/3, (n - a^2 - b^2)/2. We divide by 3 or 2, respectively, because of the number of values in the tuple not yet 'fixed'.

    An example:

    doing this on n = 12:

    1a. max_a = 3
    1b. min_a = 2
    2. for a in range(2, 3):
        use a = 2
    3a. we now look at (12 - 2^2) = 8
    max_b = 2
    3b. min_b = 2
    4. b must be 2
    5a. we now look at (12 - 2^2 - 2^2) = 4
    max_c = 2
    5b. min_c = 2
    6. c must be 2
    7. (n - a^2 - b^2 - c^2) = 0, hence d = 0
    so a possible tuple is (2, 2, 2, 0)
    
    2. use a = 3
    3a. we now look at (12 - 3^2) = 3
    max_b = 1
    3b. min_b = 1
    4. b must be 1
    5a. we now look at (12 - 3^2 - 1^2) = 2
    max_c = 1
    5b. min_c = 1
    6. c must be 1
    7. (n - a^2 - b^2 - c^2) = 1, hence d = 1
    so a possible tuple is (3, 1, 1, 1)
    

    These are the only two possible tuples - hey presto!

    0 讨论(0)
提交回复
热议问题