Làm thế nào để tìm số lần di chuyển tối thiểu để di chuyển một mục vào một vị trí trong ngăn xếp?


12

Ngăn xếp

Cho một tập hợp các ngăn xếp NXP với N là số lượng ngăn xếp và P là dung lượng ngăn xếp, làm thế nào tôi có thể tính được số lượng hoán đổi tối thiểu cần thiết để di chuyển từ một nút ở vị trí A đến một vị trí B tùy ý? Tôi đang thiết kế một trò chơi, và mục tiêu cuối cùng là sắp xếp tất cả các ngăn xếp sao cho chúng có cùng màu.

# Let "-" represent blank spaces, and assume the stacks are
stacks = [
           ['R', 'R', 'R', 'R'], 
           ['Y', 'Y', 'Y', 'Y'], 
           ['G', 'G', 'G', 'G'], 
           ['-', '-', '-', 'B'], 
           ['-', 'B', 'B', 'B']
         ]

Nếu tôi muốn chèn "B" stacks[1][1]như vậy stacks[1] = ["-", "B", "Y", "Y"]. Làm thế nào tôi có thể xác định số lượng di chuyển tối thiểu cần thiết để làm như vậy?

Tôi đã xem xét nhiều cách tiếp cận, tôi đã thử các thuật toán di truyền tạo ra tất cả các động thái có thể từ một trạng thái, ghi điểm và sau đó tiếp tục các con đường ghi điểm tốt nhất, tôi cũng đã cố gắng chạy thuật toán của Djikstra để tìm ra vấn đề . Nó có vẻ đơn giản một cách khó chịu, nhưng tôi không thể tìm ra cách để nó chạy trong bất cứ thứ gì ngoài thời gian theo cấp số nhân. Có một thuật toán tôi đang thiếu có thể áp dụng ở đây không?

Biên tập

Tôi đã viết hàm này để tính số lần di chuyển tối thiểu cần thiết: ngăn xếp: Danh sách danh sách các ký tự đại diện cho các mảnh trong ngăn xếp, ngăn xếp [0] [0] là đỉnh của ngăn xếp [0] stack_ind: Chỉ mục của ngăn xếp mà mảnh sẽ được thêm vào nhu cầu: mảnh cần được thêm vào ngăn xếp cần_index: Chỉ mục nơi mảnh được đặt

def calculate_min_moves(stacks, stack_ind, needs_piece, needs_index):
    # Minimum moves needed to empty the stack that will receive the piece so that it can hold the piece
    num_removals = 0
    for s in stacks[stack_ind][:needs_index+1]:
        if item != "-":
            num_removals += 1

    min_to_unlock = 1000
    unlock_from = -1
    for i, stack in enumerate(stacks):
        if i != stack_ind:
            for k, piece in enumerate(stack):
                if piece == needs_piece:
                    if k < min_to_unlock:
                        min_to_unlock = k
                        unlock_from = i

    num_free_spaces = 0
    free_space_map = {}

    for i, stack in enumerate(stacks):
        if i != stack_ind and i != unlock_from:
            c = stack.count("-")
            num_free_spaces += c
            free_space_map[i] = c

    if num_removals + min_to_unlock <= num_free_spaces:
        print("No shuffling needed, there's enough free space to move all the extra nodes out of the way")
    else:
        # HERE
        print("case 2, things need shuffled")

Chỉnh sửa: Các trường hợp thử nghiệm trên ngăn xếp:

stacks = [
           ['R', 'R', 'R', 'R'], 
           ['Y', 'Y', 'Y', 'Y'], 
           ['G', 'G', 'G', 'G'], 
           ['-', '-', '-', 'B'], 
           ['-', 'B', 'B', 'B']
         ]

Case 1: stacks[4][1] should be 'G'
Move 'B' from stacks[4][1] to stacks[3][2]
Move 'G' from stacks[2][0] to stacks[4][1]
num_removals = 0 # 'G' is directly accessible as the top of stack 2
min_to_unlock = 1 # stack 4 has 1 piece that needs removed
free_spaces = 3 # stack 3 has free spaces and no pieces need moved to or from it
moves = [[4, 3], [2, 4]]
min_moves = 2
# This is easy to calculate
Case 2: stacks[0][3] should be 'B'
Move 'B' from stacks[3][3] to stack[4][0]
Move 'R' from stacks[0][0] to stacks[3][3]
Move 'R' from stacks[0][1] to stacks[3][2]
Move 'R' from stacks[0][2] to stacks[3][1]
Move 'R' from stacks[0][3] to stacks[3][0]
Move 'B' from stacks[4][0] to stacks[0][3]
num_removals = 0 # 'B' is directly accessible 
min_to_unlock = 4 # stack 0 has 4 pieces that need removed
free_spaces = 3 # If stack 3 and 4 were switched this would be 1
moves = [[3, 4], [0, 3], [0, 3], [0, 3], [0, 3], [4, 0]]
min_moves = 6
#This is hard to calculate

Việc triển khai mã thực tế không phải là phần khó khăn, nó quyết định cách triển khai thuật toán giải quyết vấn đề mà tôi đang phải vật lộn.

Theo yêu cầu của @ YonIif, tôi đã tạo ra một ý chính cho vấn đề.

Khi nó chạy, nó tạo ra một mảng ngẫu nhiên của các ngăn xếp và chọn một mảnh ngẫu nhiên cần được chèn vào một ngăn xếp ngẫu nhiên tại một vị trí ngẫu nhiên.

Chạy nó in một cái gì đó định dạng này đến bàn điều khiển.

All Stacks: [['-', '-', 'O', 'Y'], ['-', 'P', 'P', 'O'], ['-', 'P', 'O', 'Y'], ['Y', 'Y', 'O', 'P']]
Stack 0 is currently ['-', '-', 'O', 'Y']
Stack 0 should be ['-', '-', '-', 'P']

Cập nhật trạng thái

Tôi rất quyết tâm để giải quyết vấn đề này bằng cách nào đó .

Hãy nhớ rằng có nhiều cách để giảm thiểu số lượng các trường hợp, chẳng hạn như những trường hợp @Hans Olsson được đề cập trong các bình luận. Cách tiếp cận gần đây nhất của tôi đối với vấn đề này, là phát triển một bộ quy tắc tương tự như các quy tắc được đề cập và sử dụng chúng trong một thuật toán thế hệ.

Các quy tắc như:

Đừng bao giờ đảo ngược một động thái. Đi từ 1-> 0 rồi 0-> 1 (Không có ý nghĩa)

Đừng bao giờ di chuyển một mảnh hai lần liên tiếp. Không bao giờ di chuyển từ 0 -> 1 rồi 1 -> 3

Đưa ra một số di chuyển từ ngăn xếp [X] sang ngăn xếp [Y], sau đó một số lần di chuyển, sau đó chuyển từ ngăn xếp [Y] sang ngăn xếp [Z], nếu ngăn xếp [Z] ở trạng thái giống như khi di chuyển từ ngăn xếp [X] đến ngăn xếp [Y] đã xảy ra, một động thái có thể đã được loại bỏ bằng cách chuyển trực tiếp từ ngăn xếp [X] sang ngăn xếp [Z]

Hiện tại, tôi đang tiếp cận vấn đề này với nỗ lực tạo ra đủ quy tắc, rằng nó giảm thiểu số lần di chuyển "hợp lệ", đủ để có thể tính toán câu trả lời bằng thuật toán thế hệ. Nếu bất cứ ai có thể nghĩ về các quy tắc bổ sung, tôi sẽ muốn nghe họ trong các ý kiến.

Cập nhật

Nhờ câu trả lời của @RootTwo tôi đã có một chút đột phá, mà tôi sẽ phác thảo ở đây.

Lên bước đột phá

Xác định chiều cao mục tiêu vì độ sâu của mảnh mục tiêu phải được đặt trong ngăn xếp đích.

Bất cứ khi nào một số mục tiêu được đặt ở chỉ mục <= stack_height - chiều cao mục tiêu, sẽ luôn có một con đường ngắn nhất để chiến thắng thông qua phương thức clear_path ().

Let S represent some solid Piece.

I E

Stacks = [ [R, R, G], [G, G, R], [-, -, -] ]
Goal = Stacks[0][2] = R
Goal Height = 2.
Stack Height - Goal Height = 0

Đưa ra một số ngăn xếp như vậy stack[0] = R, trò chơi được chiến thắng.

                       GOAL
[ [ (S | -), (S | -), (S | -) ], [R, S, S], [(S | - ), (S | -), (S | -)] ]

Vì được biết rằng chúng luôn có ít nhất stack_height khoảng trống có sẵn, trường hợp xấu nhất có thể là:

 [ [ S, S, !Goal ], [R, S, S], [-, -, -]

Vì chúng tôi biết mảnh mục tiêu không thể ở đích đến hoặc trò chơi được thắng. Trong trường hợp đó, số lần di chuyển tối thiểu cần thiết sẽ là các lần di chuyển:

(0, 2), (0, 2), (0, 2), (1, 0)

Stacks = [ [R, G, G], [-, R, R], [-, -, G] ]
Goal = Stack[0][1] = R
Stack Height - Goal Height = 1

Đưa ra một số ngăn xếp như vậy stack[1] = R, trò chơi được chiến thắng.

              GOAL
[ [ (S | -), (S | -), S], [ (S | -), R, S], [(S | -), (S | -), (S | -)]

Chúng tôi biết có ít nhất 3 khoảng trống có sẵn, vì vậy trường hợp xấu nhất có thể xảy ra là:

[ [ S, !Goal, S], [S, R, S], [ -, -, - ]

Trong trường hợp này, số lần di chuyển tối thiểu sẽ là số lần di chuyển:

(1, 2), (0, 2), (0, 2), (1, 0)

Điều này sẽ giữ cho tất cả các trường hợp.

Do đó, vấn đề đã được giảm xuống thành vấn đề tìm số lượng di chuyển tối thiểu cần thiết để đặt mảnh mục tiêu ở hoặc cao hơn ở độ cao mục tiêu.

Điều này phân chia vấn đề thành một loạt các vấn đề phụ:

  1. Khi ngăn xếp đích có phần có thể truy cập được! = Mục tiêu, xác định xem có vị trí hợp lệ cho phần đó hay không, nếu phần đó sẽ ở đó trong khi phần khác được hoán đổi.

  2. Khi ngăn xếp đích có phần có thể truy cập == phần mục tiêu, xác định xem nó có thể được gỡ bỏ và đặt ở độ cao mục tiêu yêu cầu hay không, nếu phần đó nên ở lại trong khi phần khác được hoán đổi.

  3. Khi hai trường hợp trên yêu cầu hoán đổi một mảnh khác, xác định mảnh nào sẽ hoán đổi để tăng lên để mảnh mục tiêu có thể đạt được chiều cao mục tiêu.

Ngăn xếp đích phải luôn luôn được đánh giá các trường hợp đầu tiên.

I E

stacks = [ [-, R, G], [-, R, G], [-, R, G] ]

Goal = stacks[0][1] = G

Kiểm tra Ngăn xếp Mục tiêu trước tiên dẫn đến:

(0, 1), (0, 2), (1, 0), (2, 0) = 4 Moves

Bỏ qua ngăn xếp mục tiêu:

(1, 0), (1, 2), (0, 1), (0, 1), (2, 0) = 5 Moves

2
Bạn đã thử A * chưa? Nó khá giống với thuật toán của Dijkstra nhưng đôi khi nó nhanh hơn đáng kể.
Yonlif

1
Bạn có thể vui lòng chia sẻ một liên kết repo github? Tôi muốn thử nghiệm bản thân nếu nó ổn. @Tristen
Yonlif

1
Sau cái nhìn đầu tiên, vấn đề này có vẻ NP-hard. Nó có thể không nằm trong NP (không phải NP-đầy đủ), vì ngay cả khi tôi cung cấp cho bạn một giải pháp tối ưu, bạn thậm chí không thể xác minh dễ dàng. Điều này nổi tiếng với các vấn đề tối ưu hóa về hoán vị. Tôi đề nghị đăng bài chéo tại CS . Nhìn vào các thuật toán xấp xỉ cho vấn đề này. Đây là một vấn đề khá khó khăn nhưng tồn tại một xấp xỉ khá. Điều này tương tự: Tháp tùy ý của Hà Nội
DarioHett

1
@DarioHett Đó là điều tôi lo lắng! Tôi đã có những ngón tay của mình vượt qua rằng nó sẽ không phải là một vấn đề NP-Hard, nhưng tôi cũng có cảm giác như đó có thể là một vấn đề. Tôi đã có may mắn hơn với một thuật toán di truyền, và một số chức năng ghi điểm chuyên biệt để ghi điểm di chuyển. Tôi sẽ nhìn vào Tháp tùy ý của Hà Nội! Cám ơn vì sự gợi ý.
Tristen

1
Nếu bạn cố gắng tạo ra câu đố một cách ngẫu nhiên - hãy nhớ loại bỏ các bước di chuyển rõ ràng (di chuyển thứ gì đó trở lại sau khi di chuyển về phía trước hoặc thực hiện một bước trong hai bước khi một người sẽ đủ, và cũng kết hợp với các động tác có thể không liên quan trộn lẫn vào nhau).
Hans Olsson

Câu trả lời:


1

Tôi đã đưa ra hai lựa chọn, nhưng không ai trong số họ có thể giải quyết trường hợp 2 một cách kịp thời. Tùy chọn đầu tiên là sử dụng A * với thước đo khoảng cách chuỗi là h (n) của bạn, tùy chọn thứ hai là IDA *. Tôi đã thử nghiệm nhiều biện pháp tương tự chuỗi, tôi đã sử dụng smith-waterman theo cách tiếp cận của mình. Tôi đã thay đổi ký hiệu của bạn để xử lý vấn đề nhanh hơn. Tôi đã thêm số vào cuối mỗi chữ số để kiểm tra xem một mảnh đã được di chuyển hai lần.

Dưới đây là các trường hợp tôi đã thử nghiệm trên:

start = [
 ['R1', 'R2', 'R3', 'R4'], 
 ['Y1', 'Y2', 'Y3', 'Y4'], 
 ['G1', 'G2', 'G3', 'G4'], 
 ['B1'], 
 ['B2', 'B3', 'B4']
]

case_easy = [
 ['R', 'R', 'R', 'R'], 
 ['Y', 'Y', 'Y', 'Y'], 
 ['G', 'G', 'G'], 
 ['B', 'B'], 
 ['B', 'B', 'G']
]


case_medium = [
 ['R', 'R', 'R', 'R'], 
 ['Y', 'Y', 'Y', 'B'], 
 ['G', 'G', 'G'], 
 ['B'],
 ['B', 'B', 'G', 'Y']
]

case_medium2 = [
 ['R', 'R', 'R' ], 
 ['Y', 'Y', 'Y', 'B'], 
 ['G', 'G' ], 
 ['B', 'R', 'G'],
 ['B', 'B', 'G', 'Y']
]

case_hard = [
 ['B'], 
 ['Y', 'Y', 'Y', 'Y'], 
 ['G', 'G', 'G', 'G'], 
 ['R','R','R', 'R'], 
 ['B','B', 'B']
]

Đây là mã A *:

from copy import deepcopy
from heapq import *
import time, sys
import textdistance
import os

def a_star(b, goal, h):
    print("A*")
    start_time = time.time()
    heap = [(-1, b)]
    bib = {}
    bib[b.stringify()] = b

    while len(heap) > 0:
        node = heappop(heap)[1]
        if node == goal:
            print("Number of explored states: {}".format(len(bib)))
            elapsed_time = time.time() - start_time
            print("Execution time {}".format(elapsed_time))
            return rebuild_path(node)

        valid_moves = node.get_valid_moves()
        children = node.get_children(valid_moves)
        for m in children:
          key = m.stringify()
          if key not in bib.keys():
            h_n = h(key, goal.stringify())
            heappush(heap, (m.g + h_n, m)) 
            bib[key] = m

    elapsed_time = time.time() - start_time
    print("Execution time {}".format(elapsed_time))
    print('No Solution')

Đây là mã IDA *:

#shows the moves done to solve the puzzle
def rebuild_path(state):
    path = []
    while state.parent != None:
        path.insert(0, state)
        state = state.parent
    path.insert(0, state)
    print("Number of steps to solve: {}".format(len(path) - 1))
    print('Solution')

def ida_star(root, goal, h):
    print("IDA*")
    start_time = time.time()
    bound = h(root.stringify(), goal.stringify())
    path = [root]
    solved = False
    while not solved:
        t = search(path, 0, bound, goal, h)
        if type(t) == Board:
            solved = True
            elapsed_time = time.time() - start_time
            print("Execution time {}".format(elapsed_time))
            rebuild_path(t)
            return t
        bound = t

def search(path, g, bound, goal, h):

    node = path[-1]
    time.sleep(0.005)
    f = g + h(node.stringify(), goal.stringify())

    if f > bound: return f
    if node == goal:
        return node

    min_cost = float('inf')
    heap = []
    valid_moves = node.get_valid_moves()
    children = node.get_children(valid_moves)
    for m in children:
      if m not in path:
        heappush(heap, (m.g + h(m.stringify(), goal.stringify()), m)) 

    while len(heap) > 0:
        path.append(heappop(heap)[1])
        t = search(path, g + 1, bound, goal, h)
        if type(t) == Board: return t
        elif t < min_cost: min_cost = t
        path.pop()
    return min_cost

class Board:
  def __init__(self, board, parent=None, g=0, last_moved_piece=''):
    self.board = board
    self.capacity = len(board[0])
    self.g = g
    self.parent = parent
    self.piece = last_moved_piece

  def __lt__(self, b):
    return self.g < b.g

  def __call__(self):
    return self.stringify()

  def __eq__(self, b):
    if self is None or b is None: return False
    return self.stringify() == b.stringify()

  def __repr__(self):
    return '\n'.join([' '.join([j[0] for j in i]) for i in self.board])+'\n\n'

  def stringify(self):
    b=''
    for i in self.board:
      a = ''.join([j[0] for j in i])
      b += a + '-' * (self.capacity-len(a))

    return b

  def get_valid_moves(self):
    pos = []
    for i in range(len(self.board)):
      if len(self.board[i]) < self.capacity:
        pos.append(i)
    return pos

  def get_children(self, moves):
    children = []
    for i in range(len(self.board)):
      for j in moves:
        if i != j and self.board[i][-1] != self.piece:
          a = deepcopy(self.board)
          piece = a[i].pop()
          a[j].append(piece)
          children.append(Board(a, self, self.g+1, piece))
    return children

Sử dụng:

initial = Board(start)
final1 = Board(case_easy)
final2 = Board(case_medium)
final2a = Board(case_medium2)
final3 = Board(case_hard)

x = textdistance.gotoh.distance

a_star(initial, final1, x)
a_star(initial, final2, x)
a_star(initial, final2a, x)

ida_star(initial, final1, x)
ida_star(initial, final2, x)
ida_star(initial, final2a, x)

0

Trong các ý kiến ​​bạn nói có N ngăn xếp có dung lượng P và luôn có P khoảng trống. Nếu đó là trường hợp, có vẻ như thuật toán này sẽ hoạt động trong elsemệnh đề trong mã của bạn (tức là khi nào num_removals + min_to_unlock > num_free_spaces):

  1. Tìm mảnh mong muốn gần đầu ngăn xếp nhất.
  2. Di chuyển tất cả các mảnh từ phía trên mảnh mong muốn theo cách có một ngăn xếp (không phải ngăn xếp đích) có một khoảng trống trên đầu. Nếu cần, di chuyển các mảnh từ ngăn xếp đích hoặc ngăn xếp khác. Nếu không gian mở duy nhất là đỉnh của ngăn xếp đích, hãy di chuyển một mảnh ở đó để mở đỉnh của ngăn xếp khác. Điều này luôn luôn có thể, bởi vì có P không gian mở và nhiều nhất là các mảnh P-1 để di chuyển từ phía trên mảnh mong muốn.
  3. Di chuyển mảnh mong muốn đến vị trí trống trên đỉnh ngăn xếp.
  4. Di chuyển các mảnh từ ngăn xếp đích cho đến khi đích được mở.
  5. Di chuyển mảnh mong muốn đến đích.

Tôi đã dành vài giờ qua để tìm hiểu câu trả lời này và tôi nghĩ có thể có một cái gì đó ở đó. Nếu có thể, bạn có thể cung cấp thêm một chút thông tin về cách bạn sẽ di chuyển các mảnh nằm phía trên mảnh mong muốn không? Làm thế nào để bạn xác định ngăn xếp để di chuyển chúng đến? Có lẽ một chút psuedocode / code. Đây chắc chắn là lần gần nhất tôi cảm thấy để giải quyết điều này cho đến nay.
Tristen

0

Mặc dù tôi không tìm thấy thời gian để chứng minh điều này một cách toán học, tôi vẫn quyết định đăng bài này; hy vọng nó giúp. Cách tiếp cận là xác định tham số p giảm khi di chuyển tốt và chính xác đến 0 khi trò chơi kết thúc. Trong chương trình chỉ xem xét các động thái tốt hoặc di chuyển trung tính (không thay đổi p) và quên đi các động thái xấu (tăng p).

Vậy p là gì? Đối với mỗi cột xác định p là số khối vẫn phải được loại bỏ trước khi tất cả các màu trong cột đó là màu mong muốn. Vì vậy, giả sử chúng ta muốn các khối màu đỏ kết thúc ở cột ngoài cùng bên trái (tôi sẽ quay lại sau) và giả sử có một khối màu đỏ ở dưới cùng, sau đó là một khối màu vàng ở trên đó, một khối nữa ở trên cùng đó, và sau đó là một không gian trống. Sau đó p = 2 cho cột này (hai khối cần loại bỏ trước khi tất cả có màu đỏ). Tính p cho tất cả các cột. Đối với cột cuối cùng trống, p bằng số khối trong đó (tất cả chúng nên đi). P cho trạng thái hiện tại là tổng của tất cả p cho tất cả các cột.

Khi p = 0, tất cả các cột có cùng màu và một cột trống, vì vậy trò chơi đã kết thúc.

Bằng cách chọn các động tác làm giảm p (hoặc ít nhất là không tăng p), chúng tôi đang đi đúng hướng, theo tôi, đây là điểm khác biệt quan trọng với các thuật toán đường đi ngắn nhất: Dijkstra không biết liệu mình có đi đúng hướng với mọi đỉnh anh đang điều tra.

Vậy làm thế nào để chúng ta xác định nơi mỗi màu nên kết thúc? Về cơ bản bằng cách xác định p cho mọi khả năng. Vì vậy, ví dụ bắt đầu với màu đỏ / vàng / xanh / trống, tính p, sau đó chuyển sang đỏ / vàng / trống / xanh, tính p, v.v. Lấy vị trí bắt đầu với p thấp nhất. Cái này mất n! tính toán. Với n = 8, đây là 40320, có thể thực hiện được. Tin xấu là bạn sẽ phải kiểm tra tất cả các vị trí bắt đầu với p thấp nhất bằng nhau. Tin tốt là bạn có thể quên phần còn lại.

Có hai sự không chắc chắn về toán học ở đây. Một: có thể có một con đường ngắn hơn sử dụng một động thái xấu? Có vẻ như không thể, tôi đã không tìm thấy một ví dụ, nhưng tôi cũng không tìm thấy bằng chứng. Hai: có thể là khi bắt đầu với vị trí bắt đầu không tối ưu (nghĩa là không phải p thấp nhất) sẽ có một con đường ngắn hơn so với tất cả các vị trí bắt đầu tối ưu. Một lần nữa: không có ví dụ nhưng cũng không có bằng chứng.

Một số gợi ý thực hiện. Theo dõi p trong khi thực hiện cho mỗi cột không khó nhưng tất nhiên nên được thực hiện. Một tham số khác cần được giữ cho mỗi cột là số lượng điểm mở. Nếu 0, thì cột này có thể không chấp nhận bất kỳ khối nào trong giây lát, do đó có thể bị loại khỏi vòng lặp. Khi p = 0 cho một cột, nó không đủ điều kiện cho một pop. Đối với mỗi pop có thể, kiểm tra xem có một động thái tốt hay không, tức là làm giảm tổng p. Nếu có nhiều, kiểm tra tất cả. Nếu không có, xem xét tất cả các động thái trung lập.

Tất cả điều này sẽ làm giảm đáng kể thời gian tính toán của bạn.


1
Tôi nghĩ bạn đã hiểu nhầm câu hỏi! Mặc dù đây là động lực đằng sau câu hỏi. Câu hỏi là tìm số lượng di chuyển tối thiểu để di chuyển một mảnh, đến một vị trí. Câu hỏi không phải là tìm ra số lượng di chuyển tối thiểu để sắp xếp các ngăn xếp, mặc dù đó là động lực đằng sau câu hỏi. Tuy nhiên, với điểm P đó, bạn sẽ không chính xác. Có nhiều trường hợp có những "động thái xấu" cuối cùng làm tăng P, và sau đó giảm nó với tốc độ nhanh hơn. Như đã nói, có lẽ đọc lại câu hỏi vì câu trả lời của bạn không có liên quan.
Tristen

1
Tôi xin lỗi Tristen, tôi thực sự đã không đọc kỹ câu hỏi. Tôi bị cuốn hút bởi khía cạnh toán học của nó và, đến trễ bữa tiệc, quá nhanh để trả lời. Lần sau tôi sẽ cẩn thận hơn. Hy vọng bạn tìm thấy một câu trả lời.
Paul Rene
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.