Số lượng kết quả số có thể có của dấu ngoặc đơn là 2 ^ 2 ^ Sn ^ 2


19

Hãy xem xét một biểu thức 2^2^...^2với các ntoán tử ^. Toán tử ^có nghĩa là lũy thừa ("với sức mạnh của"). Giả sử rằng nó không có tính đồng nhất mặc định, vì vậy biểu thức cần được ngoặc hoàn toàn để trở nên rõ ràng. Số cách để ngoặc đơn biểu thức được đưa ra bằng số Catalan C_n=(2n)!/(n+1)!/n! .

Đôi khi, các dấu ngoặc đơn khác nhau cho cùng một kết quả số (2^2)^(2^2)=((2^2)^2)^2, vì vậy, số lượng kết quả số khác nhau có thể có cho một số nít hơn C_ncho tất cả n>1. Trình tự bắt đầu 1, 1, 2, 4, 8, ...trái ngược với số Catalan1, 2, 5, 14, 42, ...

Vấn đề là viết chương trình (hoặc hàm) nhanh nhất chấp nhận nlàm đầu vào và trả về số lượng kết quả số khác nhau có thể có của biểu thức 2^2^...^2với các ntoán tử ^. Hiệu suất không nên suy giảm đáng kể khi nphát triển, vì vậy tính toán trực tiếp các tháp năng lượng cao có lẽ là một ý tưởng tồi.


Tôi chỉ chia sẻ một ý tưởng ở đây, nhưng có vẻ như chỉ có thể sử dụng phép cộng và phép nhân, vì câu trả lời sẽ luôn có dạng 2^n, và do đó sẽ không cần thiết phải theo dõi bất cứ điều gì ngoại trừ n. Tức là, chỉ sử dụng các quy tắc lũy thừa có vẻ khôn ngoan. Tuy nhiên, chắc chắn có một cách thông minh hơn và hoàn toàn đại số để làm điều này.
Dành cho

@Fors Tôi đoán nvẫn còn cách quá lớn để tính toán. Tuy nhiên, cũng lưu ý. Có thể một biểu diễn đệ quy ở dạng "1 hoặc 2 ^ (...) hoặc (...) + (...)"; nhưng bạn vẫn có vấn đề làm thế nào để bình thường hóa biểu diễn đó của một số (hoặc so sánh hai biểu diễn cho đẳng thức giá trị).
John Dvorak

4
@JanDvorak, A002845 (không có hình thức đóng)
Peter Taylor


1
@Vladimir Reshetnikov: Tôi nghĩ rằng có một lỗi trong công thức của bạn. Khi bạn có ntwos và C_n=(2n)!/(n+1)!/n!nên là số ngoặc đơn, thì với n = 3 thì phải là 5, đúng không? Tôi thấy (2^2)^22^(2^2), nhưng ba kết hợp khác là gì? Tôi nghĩ rằng C_n cung cấp cho bạn số lượng dấu ngoặc đơn cho n + 1 twos.
Martin Thoma

Câu trả lời:


9

Python 2.7

Cách tiếp cận này tận dụng các cân nhắc sau:

Bất kỳ số nguyên nào cũng có thể được biểu diễn dưới dạng tổng lũy ​​thừa của hai. Số mũ trong lũy ​​thừa của hai cũng có thể được biểu diễn dưới dạng lũy ​​thừa của hai. Ví dụ:

8 = 2^3 = 2^(2^1 + 2^0) = 2^(2^(2^0) + 2^0)

Các biểu thức mà chúng tôi kết thúc có thể được biểu diễn dưới dạng tập hợp các tập hợp (trong Python, tôi đã sử dụng tích hợp sẵn frozenset):

  • 0trở thành tập hợp trống {}.
  • 2^atrở thành tập hợp chứa tập đại diện a. Vd: 1 = 2^0 -> {{}}2 = 2^(2^0) -> {{{}}}.
  • a+btrở thành sự kết hợp của các bộ đại diện ab. Ví dụ,3 = 2^(2^0) + 2^0 -> {{{}},{}}

Nó chỉ ra rằng các biểu thức của biểu mẫu 2^2^...^2có thể dễ dàng được chuyển đổi thành biểu diễn tập duy nhất của chúng, ngay cả khi giá trị số quá lớn để được lưu trữ dưới dạng một số nguyên.


Đối với n=20, điều này chạy trong 8,7 giây trên CPython 2.7.5 trên máy của tôi (chậm hơn một chút trong Python 3 và chậm hơn nhiều trong PyPy):

"""Analyze the expressions given by parenthesizations of 2^2^...^2.

Set representation:  s is a set of sets which represents an integer n.  n is
  given by the sum of all 2^m for the numbers m represented by the sets
  contained in s.  The empty set stands for the value 0.  Each number has
  exactly one set representation.

  In Python, frozensets are used for set representation.

  Definition in Python code:
      def numeric_value(s):
          n = sum(2**numeric_value(t) for t in s)
          return n"""

import itertools


def single_arg_memoize(func):
    """Fast memoization decorator for a function taking a single argument.

    The metadata of <func> is *not* preserved."""

    class Cache(dict):
        def __missing__(self, key):
            self[key] = result = func(key)
            return result
    return Cache().__getitem__


def count_results(num_exponentiations):
    """Return the number of results given by parenthesizations of 2^2^...^2."""
    return len(get_results(num_exponentiations))

@single_arg_memoize
def get_results(num_exponentiations):
    """Return a set of all results given by parenthesizations of 2^2^...^2.

    <num_exponentiations> is the number of exponentiation operators in the
    parenthesized expressions.

    The result of each parenthesized expression is given as a set.  The
    expression evaluates to 2^(2^n), where n is the number represented by the
    given set in set representation."""

    # The result of the expression "2" (0 exponentiations) is represented by
    # the empty set, since 2 = 2^(2^0).
    if num_exponentiations == 0:
        return {frozenset()}

    # Split the expression 2^2^...^2 at each of the first half of
    # exponentiation operators and parenthesize each side of the expession.
    split_points = xrange(num_exponentiations)
    splits = itertools.izip(split_points, reversed(split_points))
    splits_half = ((left_part, right_part) for left_part, right_part in splits
                                           if left_part <= right_part)

    results = set()
    results_add = results.add
    for left_part, right_part in splits_half:
        for left in get_results(left_part):
            for right in get_results(right_part):
                results_add(exponentiate(left, right))
                results_add(exponentiate(right, left))
    return results


def exponentiate(base, exponent):
    """Return the result of the exponentiation of <operands>.

    <operands> is a tuple of <base> and <exponent>.  The operators are each
    given as the set representation of n, where 2^(2^n) is the value the
    operator stands for.

    The return value is the set representation of r, where 2^(2^r) is the
    result of the exponentiation."""

    # Where b is the number represented by <base>, e is the number represented
    # by <exponent> and r is the number represented by the return value:
    #   2^(2^r) = (2^(2^b)) ^ (2^(2^e))
    #   2^(2^r) = 2^(2^b * 2^(2^e))
    #   2^(2^r) = 2^(2^(b + 2^e))
    #   r = b + 2^e

    # If <exponent> is not in <base>, insert it to arrive at the set with the
    # value: b + 2^e.  If <exponent> is already in <base>, take it out,
    # increment e by 1 and repeat from the start to eventually arrive at:
    #   b - 2^e + 2^(e+1) =
    #   b + 2^e
    while exponent in base:
        base -= {exponent}
        exponent = successor(exponent)
    return base | {exponent}

@single_arg_memoize
def successor(value):
    """Return the successor of <value> in set representation."""
    # Call exponentiate() with <value> as base and the empty set as exponent to
    # get the set representing (n being the number represented by <value>):
    #   n + 2^0
    #   n + 1
    return exponentiate(value, frozenset())


def main():
    import timeit
    print timeit.timeit(lambda: count_results(20), number=1)
    for i in xrange(21):
        print '{:.<2}..{:.>9}'.format(i, count_results(i))

if __name__ == '__main__':
    main()

(Khái niệm của trình trang trí ghi nhớ được sao chép từ http://code.activestate.com/recipes/578231-probossible-the-fastest-memoization-decorator-in-the-/ .)

Đầu ra:

8.667753234
0...........1
1...........1
2...........1
3...........2
4...........4
5...........8
6..........17
[...]
19.....688366
20....1619087

Thời gian cho khác nhau n:

 n    time
16    0.240
17    0.592
18    1.426
19    3.559
20    8.668
21   21.402

Bất kỳ ntrên 21 kết quả trong một lỗi bộ nhớ trên máy của tôi.

Tôi sẽ quan tâm nếu ai đó có thể làm điều này nhanh hơn bằng cách dịch nó sang một ngôn ngữ khác.

Chỉnh sửa: Tối ưu hóa get_resultschức năng. Ngoài ra, sử dụng Python 2.7.5 thay vì 2.7.2 khiến nó chạy nhanh hơn một chút.


Tôi đã thực hiện một bản dịch C # nhưng sử dụng các mảng được sắp xếp và thực hiện bổ sung theo thứ tự thay vì theo bộ chứa các kiểm tra. Nó chậm hơn nhiều, và tôi chưa tìm hiểu xem liệu đó có phải là do không ghi nhớ chức năng kế nhiệm hay do chi phí so sánh.
Peter Taylor

1
Tôi đã không mã hóa mã @ flornquake (rực rỡ), nhưng tôi cho rằng phần lớn thời gian CPU được dành để thực hiện các thử nghiệm thành viên và thiết lập các thao tác thao tác, cả hai đều được tối ưu hóa khá tốt trong Python, sử dụng bảng băm phổ biến và khóa băm phổ biến của nó thói quen. Ghi nhớ chắc chắn là một điều lớn, với một thuật toán theo cấp số nhân như thế này. Nếu bạn bỏ nó ra, bạn có thể mong đợi hiệu suất chậm hơn theo cấp số nhân.
Tobia

@Tobia, thực sự tôi thấy rằng trong C # ghi nhớ chức năng kế nhiệm làm cho nó chậm hơn. Tôi cũng thấy rằng một bản dịch theo nghĩa đen hơn (sử dụng các thao tác thiết lập) chậm hơn đáng kể so với bổ sung cấp thấp hơn của tôi. Cải tiến thực sự duy nhất tôi tìm thấy đối với mã ban đầu của mình là tính đến (a^b)^c = (a^c)^bvà nó vẫn chậm hơn nhiều so với triển khai Python này.
Peter Taylor

@PeterTaylor: Chỉnh sửa: Theo như tôi có thể thấy, thuật toán của flornquake dựa vào việc xây dựng các bộ cây, trong đó một cây là một tập hợp các cây, v.v. Tất cả các mảnh của những cây này, từ bộ trống nhỏ nhất đến bộ lớn nhất, đều được ghi nhớ. Điều này có nghĩa là tất cả các cây này đều chứa "cấu trúc lặp lại" chỉ được tính một lần (bởi CPU) và được lưu trữ một lần (trong RAM). Bạn có chắc chắn rằng thuật toán "bổ sung theo thứ tự" của bạn đang xác định tất cả cấu trúc lặp lại này và tính toán nó một lần không? (cái mà tôi gọi là độ phức tạp theo cấp số nhân ở trên) Xem thêm en.wikipedia.org/wiki/Docate_programming
Tobia

@Tobia, chúng tôi chồng chéo. Tôi đã đăng mã.
Peter Taylor

5

C #

Đây là bản dịch mã Python của flornquake sang C # bằng cách sử dụng thói quen bổ sung cấp thấp hơn, cung cấp tốc độ vừa phải so với bản dịch trực tiếp. Đây không phải là phiên bản tối ưu hóa nhất mà tôi có, nhưng nó dài hơn một chút vì nó phải lưu trữ cấu trúc cây cũng như các giá trị.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sandbox {
    class PowerTowers {
        public static void Main() {
            DateTime start = DateTime.UtcNow;
            for (int i = 0; i < 17; i++)
                Console.WriteLine("{2}: {0} (in {1})", Results(i).Count, DateTime.UtcNow - start, i);
        }

        private static IList<HashSet<Number>> _MemoisedResults;

        static HashSet<Number> Results(int numExponentations) {
            if (_MemoisedResults == null) {
                _MemoisedResults = new List<HashSet<Number>>();
                _MemoisedResults.Add(new HashSet<Number>(new Number[] { Number.Zero }));
            }

            if (numExponentations < _MemoisedResults.Count) return _MemoisedResults[numExponentations];

            HashSet<Number> rv = new HashSet<Number>();
            for (int i = 0; i < numExponentations; i++) {
                IEnumerable<Number> rhs = Results(numExponentations - 1 - i);
                foreach (var b in Results(i))
                    foreach (var e in rhs) {
                        if (!e.Equals(Number.One)) rv.Add(b.Add(e.Exp2()));
                    }
            }
            _MemoisedResults.Add(rv);
            return rv;
        }
    }

    // Immutable
    struct Number : IComparable<Number> {
        public static Number Zero = new Number(new Number[0]);
        public static Number One = new Number(Zero);

        // Ascending order
        private readonly Number[] _Children;
        private readonly int _Depth;
        private readonly int _HashCode;

        private Number(params Number[] children) {
            _Children = children;
            _Depth = children.Length == 0 ? 0 : 1 + children[children.Length - 1]._Depth;

            int hashCode = 0;
            foreach (var n in _Children) hashCode = hashCode * 37 + n.GetHashCode() + 1;
            _HashCode = hashCode;
        }

        public Number Add(Number n) {
            // "Standard" bitwise adder built from full adder.
            // Work forwards because children are in ascending order.
            int off1 = 0, off2 = 0;
            IList<Number> result = new List<Number>();
            Number? carry = default(Number?);

            while (true) {
                if (!carry.HasValue) {
                    // Simple case
                    if (off1 < _Children.Length) {
                        if (off2 < n._Children.Length) {
                            int cmp = _Children[off1].CompareTo(n._Children[off2]);
                            if (cmp < 0) result.Add(_Children[off1++]);
                            else if (cmp == 0) {
                                carry = _Children[off1++].Add(One);
                                off2++;
                            }
                            else result.Add(n._Children[off2++]);
                        }
                        else result.Add(_Children[off1++]);
                    }
                    else if (off2 < n._Children.Length) result.Add(n._Children[off2++]);
                    else return new Number(result.ToArray()); // nothing left to add
                }
                else {
                    // carry is the (possibly joint) smallest value
                    int matches = 0;
                    if (off1 < _Children.Length && carry.Value.Equals(_Children[off1])) {
                        matches++;
                        off1++;
                    }
                    if (off2 < n._Children.Length && carry.Value.Equals(n._Children[off2])) {
                        matches++;
                        off2++;
                    }

                    if ((matches & 1) == 0) result.Add(carry.Value);
                    carry = matches == 0 ? default(Number?) : carry.Value.Add(One);
                }
            }
        }

        public Number Exp2() {
            return new Number(this);
        }

        public int CompareTo(Number other) {
            if (_Depth != other._Depth) return _Depth.CompareTo(other._Depth);

            // Work backwards because children are in ascending order
            int off1 = _Children.Length - 1, off2 = other._Children.Length - 1;
            while (off1 >= 0 && off2 >= 0) {
                int cmp = _Children[off1--].CompareTo(other._Children[off2--]);
                if (cmp != 0) return cmp;
            }

            return off1.CompareTo(off2);
        }

        public override bool Equals(object obj) {
            if (!(obj is Number)) return false;

            Number n = (Number)obj;
            if (n._HashCode != _HashCode || n._Depth != _Depth || n._Children.Length != _Children.Length) return false;
            for (int i = 0; i < _Children.Length; i++) {
                if (!_Children[i].Equals(n._Children[i])) return false;
            }

            return true;
        }

        public override int GetHashCode() {
            return _HashCode;
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.