Lập trình động với số lượng lớn các bài toán con


11

Lập trình động với số lượng lớn các bài toán con. Vì vậy, tôi đang cố gắng giải quyết vấn đề này từ Interview Street:

Đi bộ theo lưới (Điểm 50 điểm)
Bạn đang ở trong lưới chiều hai chiều tại vị trí . Kích thước của lưới là ). Trong một bước, bạn có thể đi trước một bước hoặc phía sau trong bất kỳ kích thước nào. (Vì vậy, luôn luôn có có thể di chuyển khác nhau). Trong bao nhiêu cách bạn có thể thực hiện các bước sao cho bạn không rời khỏi lưới tại bất kỳ điểm nào? Bạn rời khỏi lưới nếu với bất kỳ nào , hoặc .N(x1,x2,,xN)(D1,D2,,DNN2NMxixi0xi>Di

Thử đầu tiên của tôi là giải pháp đệ quy ghi nhớ này:

def number_of_ways(steps, starting_point):
    global n, dimensions, mem
    #print steps, starting_point
    if (steps, tuple(starting_point)) in mem:
        return mem[(steps, tuple(starting_point))]
    val = 0
    if steps == 0:
        val = 1
    else:
        for i in range(0, n):
            tuple_copy = starting_point[:]
            tuple_copy[i] += 1
            if tuple_copy[i] <= dimensions[i]:
                val += number_of_ways(steps - 1, tuple_copy)
            tuple_copy = starting_point[:]
            tuple_copy[i] -= 1
            if tuple_copy[i] > 0:
                val += number_of_ways(steps - 1, tuple_copy)
    mem[(steps, tuple(starting_point))] = val
    return val

Bất ngờ lớn: nó thất bại đối với một số lượng lớn các bước và / hoặc kích thước do thiếu bộ nhớ.

Vì vậy, bước tiếp theo là cải thiện giải pháp của tôi bằng cách sử dụng lập trình động. Nhưng trước khi bắt đầu, tôi đang thấy một vấn đề lớn với cách tiếp cận. Đối số starting_pointlà một -tuple, trong đó lớn bằng . Vì vậy, trên thực tế, chức năng có thể là với .n 10 1 x i100nn10number_of_ways(steps, x1, x2, x3, ... x10)1xi100

Các vấn đề lập trình động mà tôi đã thấy trong sách giáo khoa hầu hết đều có biến twp, do đó chỉ cần một ma trận hai chiều. Trong trường hợp này, sẽ cần một ma trận mười chiều. Vậy tổng cộng ô.10010

Với ma trận 2 chiều trong lập trình động, thường chỉ cần hàng tính toán trước đó cho phép tính tiếp theo, do đó giảm độ phức tạp không gian từ xuống . Tôi không chắc làm thế nào tôi sẽ làm như vậy trong trường hợp này. Hình dung một bảng không khả thi, vì vậy câu trả lời sẽ phải đến trực tiếp từ phép đệ quy ở trên.mnmin(m,n)

CẬP NHẬT

Sử dụng các đề xuất của Peter Shor và thực hiện một số chỉnh sửa nhỏ, đáng chú ý là cần theo dõi vị trí trong hàm và thay vì chỉ chia kích thước thành hai bộ A và B, thực hiện phân tách một cách đệ quy, sử dụng hiệu quả phương pháp chia và chinh phục, cho đến khi đạt được trường hợp cơ sở trong đó chỉ có một thứ nguyên trong tập hợp.W(i,ti)

Tôi đã đưa ra cách thực hiện sau, vượt qua tất cả các thử nghiệm dưới thời gian thực hiện tối đa:

def ways(di, offset, steps):
    global mem, dimensions
    if steps in mem[di] and offset in mem[di][steps]:
        return mem[di][steps][offset]
    val = 0
    if steps == 0:
        val = 1
    else:
        if offset - 1 >= 1:
            val += ways(di, offset - 1, steps - 1)
        if offset + 1 <= dimensions[di]:
            val += ways(di, offset + 1, steps - 1)
    mem[di][steps][offset] = val
    return val


def set_ways(left, right, steps):
    # must create t1, t2, t3 .. ti for steps
    global mem_set, mem, starting_point
    #print left, right
    #sleep(2)
    if (left, right) in mem_set and steps in mem_set[(left, right)]:
        return mem_set[(left, right)][steps]
    if right - left == 1:
        #print 'getting steps for', left, steps, starting_point[left]
        #print 'got ', mem[left][steps][starting_point[left]], 'steps'
        return mem[left][steps][starting_point[left]]
        #return ways(left, starting_point[left], steps)
    val = 0
    split_point =  left + (right - left) / 2 
    for i in xrange(steps + 1):
        t1 = i
        t2 = steps - i
        mix_factor = fact[steps] / (fact[t1] * fact[t2])
        #print "mix_factor = %d, dimension: %d - %d steps, dimension %d - %d steps" % (mix_factor, left, t1, split_point, t2)
        val += mix_factor * set_ways(left, split_point, t1) * set_ways(split_point, right, t2)
    mem_set[(left, right)][steps] = val
    return val

import sys
from time import sleep, time

fact = {}
fact[0] = 1
start = time()
accum = 1
for k in xrange(1, 300+1):
    accum *= k
    fact[k] = accum
#print 'fact_time', time() - start

data = sys.stdin.readlines()
num_tests = int(data.pop(0))
for ignore in xrange(0, num_tests):
    n_and_steps = data.pop(0)
    n, steps = map(lambda x: int(x), n_and_steps.split())
    starting_point = map(lambda x: int(x), data.pop(0).split())
    dimensions = map(lambda x: int(x), data.pop(0).split())
    mem = {}
    for di in xrange(n):
        mem[di] = {}
        for i in xrange(steps + 1):
            mem[di][i] = {}
            ways(di, starting_point[di], i)
    start = time()
    #print 'mem vector is done'
    mem_set = {}
    for i in xrange(n + 1):
        for j in xrange(n + 1):
            mem_set[(i, j)] = {}
    answer = set_ways(0, n, steps)
    #print answer
    print answer % 1000000007
    #print time() - start

2
"Nó thất bại đối với một số lượng lớn các bước và / hoặc kích thước" - "fail" nghĩa là gì ở đây?
Raphael

1
Chào mừng bạn Tôi đã chỉnh sửa câu hỏi của bạn thành a) sử dụng định dạng Markdown và LaTeX thích hợp (vui lòng để bản thân bạn trong tương lai) và b) xóa máng xối thừa. Chúng tôi không quan tâm đến những vệt mờ của mã C; xin vui lòng giới hạn bản thân vào các ý tưởng , đó là mã giả của những điều trung tâm.
Raphael

Thất bại có nghĩa là nó làm cạn kiệt tất cả bộ nhớ hệ thống có sẵn bằng cách điền vào mem[]từ điển. Và cảm ơn bạn đã làm sạch câu trả lời của tôi. Không quá quen thuộc với LaTeX nhưng sẽ nỗ lực trong lần tới.
Alexandre

Bạn có thể tìm trợ giúp về Markdown bên cạnh hộp soạn thảo; xem ở đây để biết về mồi trên LaTeX.
Raphael

Câu trả lời:


14

Các kích thước khác nhau là độc lập . Những gì bạn có thể làm là tính toán, với mỗi chiều j , có bao nhiêu bước đi khác nhau chỉ trong chiều đó có các bước . Hãy để chúng tôi gọi số đó . Từ câu hỏi của bạn, bạn đã biết cách tính những con số này bằng lập trình động.tW(j,t)

Bây giờ, thật dễ dàng để đếm số lần đi bộ thực hiện các bước theo chiều thứ . Bạn có cách xen kẽ các kích thước sao cho tổng số bước được thực hiện trong thứ nguyên là và với mỗi cách bạn có đi bộ. Tổng hợp các khoản này để nhận Bây giờ, bộ nhớ đã được kiểm soát, vì bạn chỉ cần nhớ các giá trị . Thời gian tăng trưởng đa chiều đối với lớn , nhưng hầu hết các máy tính có nhiều thời gian hơn bộ nhớ.tii(Nt1,t2,,tM)itiΠ1NW(i,ti)

t1+t2++tN=M(Mt1,t2,,tN) Πi=1NW(i,ti).
W(j,t)N

Bạn có thể làm tốt hơn nữa. Đệ quy chia kích thước thành hai tập con, và , và tính toán bao nhiêu đi có đang sử dụng chỉ là kích thước trong nhóm , và chỉ những người trong . Gọi các số này và . Bạn có tổng số lần đi bộABABWA(t)WB(t)

t1+t2=M(Mt1)WA(t1)WB(t2).

Chào Peter. Được rồi, đó là cái nhìn sâu sắc mất tích. Bây giờ tôi chỉ còn một nghi ngờ. Tổng số bên ngoài lặp lại trên tất cả các kết hợp có thể có của t1, t2, ... tn tổng đó thành M. Thật không may, số lượng kết hợp như vậy là C (M + 1, N-1), có thể cao bằng C (300 +1, 10-9). Số lượng rất lớn ... :(
Alexandre

1
@Alexandre: Thuật toán thứ hai của tôi (bắt đầu bằng "Bạn có thể làm tốt hơn nữa") không có vấn đề đó. Tôi đã để lại thuật toán đầu tiên trong câu trả lời của mình vì đó là thuật toán đầu tiên tôi nghĩ ra và bởi vì tôi nghĩ thuật toán thứ hai là một biến thể của thuật toán thứ nhất dễ dàng hơn nhiều so với việc không đưa ra động lực.
Peter Shor

Tôi đã thực hiện thuật toán thứ hai. Nó nhanh hơn, nhưng vẫn còn quá thấp cho giới hạn lớn nhất. Vấn đề với cái đầu tiên là lặp đi lặp lại trên tất cả các khả năng của t1, t2, t3, ... tn đã tóm tắt thành M. Thuật toán thứ hai chỉ lặp lại các giải pháp cho t1 + t2 = M. Nhưng sau đó, điều tương tự phải được thực hiện cho Wa (t1), lặp lại các giải pháp cho t1 '+ t2' = t1. Và cứ thế đệ quy. Đây là cách triển khai trong trường hợp bạn bị vô hiệu hóa: pastebin.com/e1BLG7Gk . Và trong thuật toán thứ hai, có đa thức nên M hơn t1, t2 không?
Alexandre

Đừng bận tâm! Giải quyết nó! Phải sử dụng ghi nhớ trong hàm set_ways. Đây là giải pháp cuối cùng, nhanh chóng! pastebin.com/GnkjjpBN Cảm ơn bạn đã thấu hiểu Peter. Bạn đã thực hiện cả hai quan sát chính: độc lập vấn đề và phân chia và chinh phục. Tôi khuyên mọi người nên xem giải pháp của tôi vì có một số điều không có trong câu trả lời ở trên, chẳng hạn như hàm W (i, ti) cần một đối số thứ ba, đó là vị trí. Điều đó phải được tính cho các kết hợp giá trị của i, ti và vị trí. Nếu bạn có thể, cũng thêm t2 đa thức trong thuật toán thứ hai của bạn.
Alexandre

4

Hãy trích xuất một công thức cho từ mã của bạn (đối với một ô bên trong, đó là bỏ qua các trường hợp viền):now(s,x1,,xn)

now(s,x1,,xn)=+i=0nnow(s1,x1,,xi1,xi+1,xi+1,,xn)+i=0nnow(s1,x1,,xi1,xi1,xi+1,,xn)

Đây là một số ý tưởng.

  • Chúng tôi thấy rằng một khi bạn đã tính toán tất cả các giá trị cho , bạn có thể loại bỏ tất cả các giá trị được tính cho .s=ks<k
  • Đối với một cố định , bạn nên tính toán các mục trong bảng theo thứ tự từ điển (chỉ vì nó đơn giản). Sau đó, lưu ý rằng mọi ô chỉ cần các ô như vậy trong "bán kính một", không có tọa độ nào có thể ở xa hơn một ô. Do đó, khi lần lặp của bạn đạt , bạn có thể bỏ tất cả các giá trị cho . Nếu điều đó là không đủ, hãy thực hiện tương tự cho - đối với cố định , bỏ các giá trị với và khi đạt đến - v.v.sx1=ix1i2x2x1=ix1=ix2j2x2=j
  • Lưu ý rằng "vì vậy luôn có khả năng di chuyển khác nhau" chỉ giữ ở giữa lưới, đó là nếu và cho tất cả . Nhưng điều đó cũng có nghĩa rằng câu trả lời là dễ dàng ở giữa: nó chỉ . Nếu bạn đã tái phát lập trình động làm việc, một mình điều đó sẽ cho phép bạn cạo đi phần lớn bảng (nếu ).2NxiM>0xi+M<Dii(2N)MMN
  • Một điều cần lưu ý là bạn không phải tính toán toàn bộ bảng; hầu hết các giá trị sẽ được điền bằng (nếu ). Bạn có thể giới hạn mình trong khối (siêu) có độ dài cạnh quanh (lưu ý rằng nó sẽ bị lõm xuống do các đường dẫn rời khỏi lưới).0MN2Mx

Điều đó là đủ để giữ cho việc sử dụng bộ nhớ khá thấp.


Xin chào Raphael, giả sử mục tiêu của chúng tôi bây giờ là (3, 3, 3, 3), trên lưới 5x5x5. Sử dụng lập trình động và sử dụng thứ tự lex như bạn đề xuất, chúng tôi sẽ tính toán ngay bây giờ (0, 0, 0, 0), sau đó (0, 0, 0, 1), ... ngay bây giờ (0, 5, 5, 5). Tại thời điểm nào chúng ta có thể loại bỏ ngay bây giờ (0, 0, 0, 0) (nhiều hơn bán kính của một khoảng cách (5, 5, 5), vì bây giờ chúng ta sẽ cần nó để tính toán ngay bây giờ (1, 0, 0 , 0), bây giờ (1, 0, 0, 1), v.v? Bạn đã đề cập đến M << N một vài lần, nhưng giới hạn là 1 <= M <= 300 và 1 <= N <= 10. Vì vậy, , ở các thái cực, dường như không có 1 << 300.
Alexandre

1) Điều gì không rõ ràng trong viên đạn thứ hai của tôi? Ngay khi bạn tính toán , bạn có thể loại bỏ . Tuy nhiên, đó không phải là điểm sớm nhất mà bạn có thể loại bỏ ; ô cuối cùng bạn cần là . 2) Tôi không quá quan tâm đến các giá trị cụ thể của bạn cho và , phải trung thực. Tôi muốn nhìn vào vấn đề chung. Nếu bạn không có , hai viên đạn cuối cùng sẽ không giúp bạn nhiều. Mặc dù vậy, và đủ để nhận thấy hiệu quả và không chiến lược nào gây tổn hại. (2,0,0,0)(0,\*,\*,\*)(0,0,0,0)(1,0,0,0)MNMNM=1N=10
Raphael

1
Tôi hiểu viên đạn 1). Điều đó làm giảm độ phức tạp không gian từ M * D ^ N đến D ^ N, nhưng D ^ N vẫn còn quá nhiều. Tôi không hoàn toàn thấy cách 2) viên đạn hoạt động. Bạn có thể sử dụng ví dụ trong bình luận của tôi để minh họa nó?
Alexandre

@Alexandre Tôi đã làm trong bình luận trước đó của tôi. Nếu tôi đọc có nghĩa là , thì việc áp dụng viên đạn thứ hai một lần làm giảm độ phức tạp không gian cho , lần thứ hai thành và Sớm. (Chính xác hơn, nó đi từ đến và cứ thế.)Dmaxi=1,,NDiDN1DN2i=1NDii=2NDi
Raphael

Không hiểu lắm về cách thực hiện ... Giả sử tôi đã hiểu và tôi đã giảm độ phức tạp không gian xuống D. Về cơ bản, liệu các bài toán con M * D ^ N có còn cần phải giải quyết không? Không phải là một tài sản bổ sung cần thiết để làm cho vấn đề đa thức?
Alexandre
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.