Thứ Tư, 5 tháng 9, 2018

Thuật toán Quy hoạch động


(Nguồn: manhhomienbienthuy.bitbucket.io)


Trong bài viết này, tôi sẽ giới thiệu với các bạn một thuật toán thần thánh: quy hoạch động. Nếu bạn tham gia các cuộc thi code, bạn nhất định phải biết thuật toán này.
Gần một nửa các bài thi trong các cuộc thi code cần đến quy hoạch động. Tất nhiên, có những cách khác để giải bài toán đó. Nhưng vì các cuộc thi code đều có giới hạn về thời gian, cũng như bộ nhớ của chương trình, nên một thuật toán hiệu quả là cực kỳ cần thiết. Và trong những trường hợp như vậy, quy hoạch động là một trong những thuật toán xuất hiện nhiều nhất.
Thuật toán quy hoạch động được ưa chuộng bởi vì ban đầu, bài toán có muôn hình vạn trạng và bạn phải suy nghĩ rất nhiều mới tìm ra được lời giải. Không có một công thức chuẩn mực nào áp dụng được cho mọi bài toán. Bởi vì sự phổ biến của nó, bạn bắt buộc phải cực kỳ thuần thục thuật toán này nếu muốn có kết quả tốt trong các cuộc thi.
Cách hiệu quả nhất để tìm hiểu một thuật toán là xem xét những ví dụ cụ thể. Trong bài viết này, tôi sẽ giới thiệu vài ví dụ trong phần sau. Có thể nó chưa đầy đủ, bạn có thể đọc thêm ở các bài viết khác. Giới thiệu với các bạn một tài liệu rất hay: Dynamic Programming: From novice to advanced

Khi nào thì dùng quy hoạch động

Khi nào thì chúng ta cần đến quy hoạch động? Đó là một câu hỏi rất khó trả lời. Không có một công thức nào cho các bài toán như vậy.
Tuy nhiên, có một số tính chất của bài toán mà bạn có thể nghĩ đến quy hoạch động. Dưới đây là hai tính chất nổi bật nhất trong số chúng:
  • Bài toán có các bài toán con gối nhau
  • Bài toán có cấu trúc con tối ưu
Thường thì một bài toán có đủ cả hai tính chất này, chúng ta có thể dùng quy hoạch động được. Một câu hỏi rất thú vị là không dùng quy hoạch động có được không? Câu trả lời là có, nhưng nếu bạn đi thi code, bạn trượt là cái chắc. Để hiểu rõ hơn, chúng ta sẽ tìm hiểu từng tính chất một trong những phần dưới đây

Bài toán con gối nhau

Tương tự như thuật toán chia để trị, quy hoạch động cũng chia bài toán lớn thành các bài toán con nhỏ hơn. Quy hoạch động được sử dụng khi các bài toán con này được gọi đi gọi lại. Phương pháp quy hoạch động sẽ lưu kết quả của bài toán con này, và khi được gọi, nó sẽ không cần phải tính lại, do đó làm giảm thời gian tính toán.
Quy hoạch động sẽ không thể áp dụng được (hoặc nói đúng hơn là áp dụng cũng không có tác dụng gì) khi các bài toán con không gối nhau. Ví dụ với thuật toán tìm kiếm nhị phân, quy hoạch động cũng không thể tối ưu được gì cả, bởi vì mỗi khi chia nhỏ bài toán lớn thành các bài toán con, mỗi bài toán cũng chỉ cần giải một lần mà không bao giờ được gọi lại.
Một ví dụ rất điển hình của bài toán con gối nhau là bài toán tính số Fibonacci. Bài toán quá nổi tiếng rồi, chúng ta có thể tính toán số Fibonacci theo đúng công thức như sau:
def fib(n):
    if n <= 1:
        return n
    return fib(n -1) + fib(n - 2)
Nếu tính toán như trên, chúng ta rất nhiều bài toán con sẽ được tính đi tính lại, điển hình là các số fib(0) và fib(1).
Và quy hoạch động chính là một trong số những phương pháp có thể giúp chúng ta tối ưu hóa quá trình tính toán này. Mỗi bài toán con (số fib) sẽ được lưu lại trước khi tính những bài toán con lớn hơn. Nhờ đó, mà việc tính toán giảm đi đáng kể, mỗi bài toán con chỉ cần tính đúng một lần.
Một ví dụ quy hoạch động với bài toán này như sau:
def fib(n):
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]
Qua ví dụ trên, bạn đã thấy được sức mạnh vượt trội của quy hoạch động chưa? Đó cũng chính là lý do mà nó rất được ưa chuộng trong các cuộc thi lập trình, khi mà thời gian và bộ nhớ đều là hữu hạn (và thường khá nhỏ).

Cấu trúc con tối ưu

Cấu trúc con tối ưu là một tính chất là lời giải của bài toán lớn sẽ là tập hợp lời giải từ các bài toán nhỏ hơn.
Mình lấy một ví dụ cho dễ hiểu:
Trong bài toán tìm đường đi ngắn nhất trong đồ thị, nếu một node x nằm trên đường đi ngắn nhất giữa hai node uv thì đường đi ngắn nhất từ uđến v sẽ là tổng hợp của đường đi ngắn nhất từ u đến x và đường đi ngắn nhất từ x đến v. Môt số thuật toán tìm đường trên đồ thị (nổi tiếng nhất có lẽ là Dijkstra) đều dựa trên tính chất này, và nó cũng áp dụng quy hoạch động.
Tính chất cấu trúc con tối ưu rất quan trọng. Nó cho phép chúng ta giải bài toán lớn dựa vào các bài toán con đã giải được. Nếu không có tính chất này, chúng ta không thể áp dụng quy hoạch động được.
Không phải bài toán nào cũng có tính chất cấu trúc con tối ưu này. Ví dụ với đồ thị sau:
Đường đi dài nhất từ q -> t sẽ là q -> r -> t hoặc q -> s -> t. Nhưng không giống như bài toán tìm đường đi ngắn nhất, đường đi dài nhất không phải là tổ hợp của những đường đi thành phần, do đó, bài toán này không có cấu trúc con tối ưu.
Ví dụ, đường q -> r -> t không phải là tổ hợp của đường đi dài nhất từ q -> r và đường đi dài nhất từ r -> t. Bởi vì, đường đi dài nhất q -> r phải là q -> s -> t -> r và đường đi dài nhất từ r -> t phải là r -> q -> s -> t.

Một số bài toán quy hoạch động

Trong phần này, chúng ta sẽ làm quen với quy hoạch động thông qua một số ví dụ cụ thể. Chúng ta sẽ xem xét cách quy hoạch động được áp dụng vào các bài toán cụ thể như thế nào, đồng thời qua đó, chúng ta sẽ hiểu hơn về các tính chất ở phần trước.

Ví dụ 1: Bài toán kinh điển với đồng xu

Đây là một ví dụ rất kinh điển khi học về quy hoạch động. Có thể có nhiều cách phát biểu khác nhau nhưng về cơ bản, nội dung của nó sẽ tương tự như sau.
Giả sử chúng ta có n đồng xu nặng lần lượt là W1, W2, ..., Wn, và bài toán đặt ra là tìm số lượng đồng xu nhỏ nhất để tổng khối lượng của chúng là một giá trị S. Tất nhiên, số lượng đồng xu là không giới hạn.
Với bài toán này, chúng ta cần xây dựng và giải các bài toán con gối nhau. Với ví dụ của chúng ta, mỗi bài toán con dp(P) với P <= S là bài toán tìm số đồng xu nhỏ nhất để khối lượng của chúng là P. và dp(P) = k chính là số lượng đồng xu nhỏ nhất đó.
Chúng ta sẽ áp dụng phương pháp quy hoạch động bằng cách bắt đầu từ bài toán con dp(0) sau đó tiếp tục với các bài toán con lớn hơn. Lời giải của các bài toán con sẽ được xây dựng lần lượt cho đến chúng ta xây dựng đến bài toán dp(S) và đó chính là kết quả của bài toán lớn. Một điều cần lưu ý với kỹ thuật này là bài toán con tiếp theo sẽ không thể giải được nếu chúng ta chưa giải bài toán con trước đó.
Cuối cùng là phần khó nhất của mọi bài toán quy hoạch động, đó là trả lời câu hỏi: cấu trúc con tối ưu của bài toán này ở đâu. Hay nói một cách khác, làm thế nào để từ những bài toán nhỏ hơn có thể tổ hợp ra lời giải cho bài toán lớn. Với vị dụ kinh điển này, mọi thứ sẽ tương đối đơn giản, nhưng với những bài toán phức tạp hơn, chúng ta cần suy nghĩ và tính toán nhiều hơn.
Quay trở lại với bài toán của chúng ta. Giả sử P là tổng khối lượng của các đồng xu nặng lần lượt là V1, V2, ..., Vj. Để có được khối lượng P, chúng ta cần thêm vài đúng 1 đồng xu nặng U vào khối lượng Q sao cho Q + U = P. Tất nhiên, bài toán con dp(Q) chúng ta đã có lời giải nên chúng ta sẽ biết được cần bao nhiêu đồng xu cho dp(P). Và vì có nhiều đồng xu U (nhiều nhưng hữu hạn) nên chúng ta có thể cần đến nhiều bài toán con trước đó, và dp(p) là giá trị nhỏ nhất sau khi tổng hợp những bài toán con đó.
Ví dụ với n = 3, S = 11, W = [1, 3, 5].
  • Bắt đầu với bài toán con 0 ta có dp(0) = 0
  • Với bài toán con 1, có 1 đồng xu (nặng 1) có thể thêm vào từ 0 đồng xu nào cả. Vậy dp(1) = dp(0) + 1 = 1.
  • Với bài toán con 2, cũng chỉ có 1 đồng xu (nặng 1) có thể thêm vào từ 1đồng xu. Vậy dp(2) = dp(1) + 1 = 2.
  • Với bài toán con 3, chúng ta có thể thêm 1 đồng xu 3 vào 0 đồng xu hoặc thêm 1 đồng xu 1 vào 2 đồng xu. Rõ ràng là cách đầu tiên cho kết quả nhỏ hơn. Vậy dp(3) = min(dp(2) + 1, dp(0) + 1) = min(3, 1) = 1
  • ...
  • Cứ tiếp tục như vậy cho đến bài toán S chính là đáp án chúng ta cần tìm.
Về mặt cài đặt, quy hoạch động thường lưu kết quả vào một mảng. Trong ví dụ của chúng ta, mảng dp[0..S] sẽ lưu kết quả cho từng bài toán con. Nói cách khác, dp[P] = k nghĩa là cần ít nhất k đồng xu để có khối lượng là P Toàn bộ mảng này sẽ được tính bằng vòng lặp. Đoạn code sau mô tả toàn bộ quá trình này.
n, S = map(int, input().split())
w = list(map(int, input().split()))
dp = [0] * (S + 1)
dp[0] = 0

for P in range(1, S + 1):
    dp[P] = min(dp[P - x] for x in w if x <= P) + 1

print(dp)
print(dp[S])

# Nếu đầu vào như sau: n = 3, S = 11, w = [1, 3, 5]
# Thì bảng lời giải cho các bài toán con sẽ lần lượt như sau:
# P = 0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11
# ------+--+--+--+--+--+--+--+--+--+--+--
# k = 0 |1 |2 |1 |2 |1 |2 |3 |2 |3 |2 |3

Ví dụ 2: Xâu con chung dài nhất (LCS)

Thêm một ví dụ nữa cho dễ, cũng là một bài toán rất nổi tiếng.
Cho hai xâu ký tự. Tìm độ dài xâu con chung nhỏ nhất giữa chúng. Ví dụ với 2 xâu "quetzalcoatl" và "tezcatlipoca" thì xâu con chung dài nhất sẽ là "ezaloa" với độ dài 6.
Với bài toán này, chúng ta sẽ lần lượt giải các bài toán con như sau:
Lấy i ký tự đầu tiên từ xâu thứ nhất và j ký tự đầu tiên từ xâu thứ hai và tìm độ dài xâu chung dài nhất giữa 2 xâu con được lấy ra đó. Dễ dàng thấy được rằng, lời giải của mỗi bài toán con sẽ phụ thuộc vào i và j, dp(i, j). Và bài toán lớn sẽ được giải bằng cách lần lượt giải các bài toán con lần lượt từ dp(0, 0) và tăng dần độ dài xâu được lấy ra cho đến khi chúng ta lấy ra toàn bộ xâu của đề bài.
Chúng ta hãy bắt đầu lần lượt các bài toán con. Đương nhiên, nếu một trong hai xâu là rỗng thì xâu con chung của chúng cũng rỗng. Vậy dp(0, j) = dp(i, 0) = 0. Nếu cả i và j đều dương, chúng ta cần suy xét một vài trường hợp.
  1. Nếu ký tự cuối cùng của xâu thứ nhất không có mặt trong xâu con chung dài nhất, nó có thể bị bỏ qua mà không ảnh hưởng gì đến kết quả. Công thức ở đây sẽ là dp(i, j) = dp(i - 1, j).
  2. Tương tự như trường hợp trên, ký tự cuối cùng của xâu thứ hai không ảnh hưởng đến kết quả thì dp(i, j) = dp(i, j - 1).
  3. Trường hợp cuối cùng, nếu hai ký tự cuối cùng của hai xâu x1, x2 đều có mặt trong xâu con chung dài nhất. Dĩ nhiên là hai ký tự này phải là một thì điều này mới xảy ra, tức là x1 == x2. Trong trường hợp này, khi xoá đi bất cứ một ký tự nào trong hai ký tự đó đều khiến xâu con chung dài nhất ngắn đi 1 ký tự. Vậy rõ ràng là dp(i, j) = dp(i - 1, j - 1) + 1.
Trong cả ba trường hợp trên, chúng ta phải chọn ra trường hợp nào cho kết quả là xâu con chung dài nhất (với bài toán này thì chỉ cần đưa ra độ dài đó là đủ).
Về mặt cài đặt, dp sẽ được lưu trong mảng hai chiều. Kết quả của mảng này sẽ được tính toán thông qua vòng lặp hai lớp. Lưu ý rằng, chúng ta cần thực hiện vòng lặp sao cho chúng ta sẽ giải lần lượt từng bài toán con một, theo thứ tự từ nhỏ đến lớn. Bởi vì mỗi bài toán con dp(i, j) đều phụ thuộc vào các bài toán con trước đó dp(i - 1, j), dp(i, j - 1), dp(i - 1, j - 1).
n1, n2 = map(int, input().split())
s1, s2 = input().split()
t = [[0] * (len(s2) + 1) for _ in range(len(s1) + 1)]
for i, x1 in enumerate(s1, 1):
    for j, x2 in enumerate(s2, 1):
        if x1 == x2:
            t[i][j] = t[i - 1][j - 1] + 1
        else:
            t[i][j] = max(t[i][j - 1], t[i - 1][j])

print(t[-1][-1])

# Kết quả khi giải các bài toán con như bảng sau:
#
#    S|    t  e  z  c  a  t  l  i  p  o  c  a
# T ji| 0  1  2  3  4  5  6  7  8  9 10 11 12
# ----+--------------------------------------
#  0 | 0  0  0  0  0  0  0  0  0  0  0  0  0
# q 1 | 0  0  0  0  0  0  0  0  0  0  0  0  0
# u 2 | 0  0  0  0  0  0  0  0  0  0  0  0  0
# e 3 | 0  0  1  1  1  1  1  1  1  1  1  1  1
# t 4 | 0  1  1  1  1  1  2  2  2  2  2  2  2
# z 5 | 0  1  1  2  2  2  2  2  2  2  2  2  2
# a 6 | 0  1  1  2  2  3  3  3  3  3  3  3  3
# l 7 | 0  1  1  2  2  3  3  4  4  4  4  4  4
# c 8 | 0  1  1  2  3  3  3  4  4  4  4  5  5
# o 9 | 0  1  1  2  3  3  3  4  4  4  5  5  5
# a 10| 0  1  1  2  3  4  4  4  4  4  5  5  6
# t 11| 0  1  1  2  3  4  5  5  5  5  5  5  6
# l 12| 0  1  1  2  3  4  5  6  6  6  6  6  6

Quy hoạch động vs. Memoization

Có một kỹ thuật khác gọi là "memoization" cũng có cách tiếp cận tương tự với quy hoạch động. Cả quy hoạch động và memoization đều dùng để tối ưu các vòng lặp mà có tính toán tượng tự nhau, trong đó kết quả của phép tính lớn hơn sẽ cần được tính toán dựa vào kết quả của phép tính nhỏ hơn. Memoization thường được sử dụng trong các phép tính đệ quy khi mà một tính toán bị lặp đi lặp lại nhiều lần. Nó sẽ lưu một bảng các giá trị tính được, mỗi khi có tính toán cần thực hiện, chúng ta sẽ tra bảng đó trước. Nếu bảng đã có kết quả rồi, chúng ta chỉ cần lấy ra là xong, nếu chưa, chúng ta sẽ tính toán như thường và tiếp tục lưu vào bảng.
Memoization không phải là một thuật toán theo đúng nghĩa, nó là một kỹ thuật được sử dụng trong lập trình thì đúng hơn. Để hiểu rõ hơn về kỹ thuật này, mình xin lấy ví dụ ngay với bài toán Fibonacci. Chúng ta sẽ sử dụng memoization như sau:
look_up = {0: 1, 1: 1}
def fib(n):
    if look_up.get(n) is None:
        look_up[n] = fib(n - 1) + fib(n - 2)
    return look_up[n]
Sự khác biệt chủ yếu là quy hoạch động sẽ thực hiện việc tính toán theo một thứ tự định trước, trong khi memoization duyệt theo chiều sâu. Quy hoạch động không bao giờ tính toán một bài toán con hai lần, tương đối giống với các phép tính đệ quy với memoization. Tuy nhiên memoization thì không bao giờ tính toán những phép tính thừa trong khi quy hoạch động sẽ cần tất cả mọi bài toán con. Đây là một phương pháp khá hay, nó chỉ tính toán những gì cần thiết và lưu kết quả này lại để sau này dùng lại khi nào được gọi mà không cần tính toán nữa.
Dưới đây là một số ưu, nhược điểm của memoization khi so sánh với quy hoạch động:
Ưu điểm
  • Dễ code hơn
  • Không yêu cầu thứ tự thực hiện tính toán
  • Chỉ tính toán những gì cần thiết
Nhược điểm
  • Chỉ có một kiểu duyệt duy nhất
  • Thường chậm hơn quy hoạch động.

Các dạng toán quy hoạch động

Phần lớn các bài toán quy hoạch động có thể chia làm hai loại: bài toán cần quy hoạch động để tối ưu và bài toán quy tổ hợp. Trong những phần dưới đây, chúng ta sẽ xem xét từng loại bài toán này.

Bài toán tối ưu

Bài toán tối ưu yêu cầu chúng ta phải tìm đáp án tốt nhất từ mục tiêu của bài toán. Cả hai ví dụ mình đưa ra ở trên đều thuộc loại bài toán này (một bài tìm số đồng xu ít nhất, một bài tìm xâu con dài nhất). Mối liên hệ của các bài toán con thuộc dạng này có công thức chúng là dp[s] = min(F1(dp[i], dp[j], ..., dp[k]), F2(dp[u], dp[v], ..., dp[w]), ..., Fl(dp[q], dp[p], ..., dp[z])), trong đó dp mảng lưu kết quả của các bài toán con đó.
Mỗi bài toán được giải dựa trên bài toán đã được giải trước đó. Đây chính là tính chất cấu trúc con tối ưu của mỗi bài toán. Với bài toán đồng xu, mỗi bài toán mới đều được giải bằng cách thêm đúng 1 đồng xu vào kết quả từ trước đó. Kết quả cuối cùng là kết quả tốt nhất thu được từ nhiều cách thêm đồng xu với khối lượng khác nhau.
Trước khi tính toán, mảng chứa kết quả có thể được điền đầy một giá trị trung tính nào đó. Giá trị trung tính có nghĩa là giá trị đó sẽ không bao giờ là đáp án cho bất kỳ bài toán con nào. Ví dụ khi cần tìm ra số đồng xu nhỏ nhất, chúng ta có thể điền mảng này bằng số dương lớn nhất, mọi tính toán tiếp theo sẽ cho ra một kết quả nhỏ hơn nhiều. Nếu không ra kết quả nào khác, chúng ta có thể coi như là không có một đáp án nào cho bài toán con đó.

Bài toán tổ hợp

Bài toán tổ hợp thường yêu cầu chúng ta tìm ra số cách khác nhau để thực hiện một việc gì đó. Nhiều bài thi code thường có kết quả rất lớn và họ yêu cầu chúng ta đưa đáp án dạng modulo của 10000007. Trong dạng bài toán này, công thức khi xây dựng các bài toán con sẽ là R[s] = F1(R[i], R[j], ..., R[k]) + F2(R[u], R[v], ..., R[w]) + ... + Fl(R[q], R[p], ..., R[z]). Sự khác biệt cơ bản của dạng bài toán này với dạng bài toán tối ưu là ở chỗ chúng ta cần tính tổng thay vì tìm số lớn nhất hoặc nhỏ nhất.
Trong mọi bài toán quy hoạch động, tính chất cấu trúc con tối ưu luôn là quan trọng nhất và cũng là tính chất khó đảm bảo nhất. Nếu cấu trúc con không được tối ưu, chúng ta sẽ tính toán theo một phương thức sai lầm và đương nhiên, kết quả thu được cũng không chính xác.
Với phần lớn các bài toán quy hoạch động, việc chia các bài toán con gối nhau khá dễ dàng trong khi đảm bảo cấu trúc con tối ưu thì khó hơn nhiều.
Mình sẽ đưa ra hai ví dụ tương tự nhau cho các bạn hiểu rõ hơn về những khó khăn để đảm bảo tính chất này.
Vẫn với bài toán đồng xu, chúng ta sẽ thay đổi một chút để có bài toán tổ hợp như sau:
Tìm số cách khác nhau để chọn ra các đồng xu sao cho tổng khối lượng của chúng là S.
Các bài toán con sẽ tương tự như trước: dp(P) = k là số cách khác nhau để chọn ra các đồng xu có tổng khối lượng là P. Công thức đệ quy trong trường hợp này sẽ biến đổi theo bài toán như sau:
# Công thức đệ quy cho bài toán quy hoạch động
#   {dp[0] = 1;
#   {dp[P] = sum(dp[P-Wi]);   (for Wi <= P)
#
# Với đầu vào như sau: n = 3, S = 11, W = [1, 3, 5]
# Mảng kết quả quy hoạch động sẽ là
#  P = 0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11
#  ------+--+--+--+--+--+--+--+--+--+--+--
#  k = 1 |1 |1 |2 |3 |5 |8 |12|19|30|47|74
Bài toán tổ hợp cũng có thể có một giá trị trung tính. Bởi vì bài toán tổ hợp thường tính tổng, giá trị trung tính sẽ là 0. Bài toán tổ hợp yêu cầu tìm số cách khác nhau để làm gì đó, do đó giá trị 0 sẽ không ảnh hưởng gì đến đáp án. Một điểm đặc biệt quan trọng trong bài toán tổ hợp này là mỗi cách chúng ta chỉ tính đúng một lần. Nói thì dễ nhưng nhiều khi trong thực hành chúng ta hay gặp sai sót ở chỗ cực kỳ quan trọng này.
Tiếp tục thay đổi thêm một chút, chúng ta sẽ có bài toán tổ hợp như sau:
Tìm số cách khác nhau để chọn ra các đồng xu sao cho tổng khối lượng của chúng là S. Với điều kiện, các cách lấy đồng xu là hoán vị của nhau không được coi là khác nhau.
Bài toán này khó hơn bài toán trước một chút. Nếu chúng vẫn chia các bài toán con như cũ thì không thể có được cấu trúc con tối ưu. Ví dụ, với các đồng xu 1, 3, 5 thì (1, 3) và (3, 1) đều cho kết quả là 4 nhưng chỉ được coi là 1 cách.
Với bài toán này, chúng ta sẽ chia bài toán lớn thành các bài toán con theo một cách tương đối khác. Chúng ta thấy rằng, kết quả (số cách chọn đồng xu) sẽ là tổng hợp của hai kết quả:
  • Số cách lấy đồng xu từ n - 1 đồng xu đầu tiên, tức là chúng ta coi như không có đồng xu nặng nhất
  • Số cách lấy đồng xu có chứa đồng xu nặng nhất.
Kết quả sẽ là tổng của hai kết quả trên. Các bạn thấy đó, với cách xây dựng bài toán con như thế này, chúng ta đã xây dựng các bài toán con gối nhau mà vẫn đảm bảo cấu trúc con tối ưu (kết quả bằng tổng của các bài toán con).
Nhân tiện, với cách chia bài toán như vậy, chúng ta có thể thu được lời giải bằng cách đệ quy đơn giản như sau:
n, S = map(int, input().split())
w = list(map(int, input().split()))


def count(arr, x):
    # Có 1 cách (lấy ra 0 đồng xu) cho tổng khối lượng bằng 0
    if x == 0:
        return 1
    # Không thể lấy được các đồng xu cho khối lượng âm
    if x < 0:
        return 0
    # Không thể lấy nếu không có đồng xu nào
    if not arr and x >= 1:
        return 0
    # Kết quả là tổ hợp các bài toán con
    return count(arr[:-1], x) + count(arr, x - arr[-1])


print(count(w, S))
Tuy nhiên, như mình đã nói ở phần trước, nếu bạn đang thi code, cách làm này sẽ không mang lại bất cứ hy vọng đạt giải nào, do nó cực kỳ mất thời gian và bộ nhớ. Tuy nhiên, chúng ta có thể áp dụng quy hoạch động cho bài toán này rất dễ dàng sau khi có được cấu trúc con tối ưu với các bài toán con gối nhau:
n, S = map(int, input().split())
w = list(map(int, input().split()))

dp = [[0 for _ in range(n)] for _ in range(S + 1)]
for i in range(n):
    dp[0][i] = 1

for i in range(1, S + 1):
    for j in range(n):
        x = dp[i - w[j]][j] if i - w[j] >= 0 else 0
        y = dp[i][j - 1] if j >= 1 else 0
        dp[i][j] = x + y


print(dp[-][n - 1])
# Kết quả tính toán với n = 3, w = [1, 3, 5] như sau:
#  S = 0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |10|11
#  ------+--+--+--+--+--+--+--+--+--+--+--
#  k = 1 |1 |1 |2 |2 |3 |4 |4 |5 |6 |7 |8
Các bạn thấy đó, xây dựng các bài toán con gối nhau sao cho cấu trúc con vẫn tối ưu nhiều khi không đơn giản chút nào. Và mỗi bài toán quy hoạch động lại có những biến hóa khác nhau mà không theo một khuôn mẫu khô cứng nào. Ngay cả khi bạn có thể giải được rất nhiều bài toán quy hoạch động rồi, không gì có thể đảm bảo bạn có thể giải được các bài khác nữa. Đó cũng là một lý do khiến cho dạng bài này luôn "hot" trong các cuộc thi.

Quy hoạch động xuôi và ngược

Tất cả những ví dụ mình đã trình bày ở trên đều sử dụng quy hoạch động kiểu “ngược”. Ngược ở đây không phải là chúng ta duyệt các bài toán con từ lớn ngược về nhỏ. Mà quy trình sẽ như thế này: Duyệt qua tất cả các bài toán con (từ nhỏ đến lớn), với mỗi bài toán đó, chúng ta tính toán kết quả dựa vào bài toán con trước đó. Tất nhiên, bài toán con phía trước đã được giải theo quy trình duyệt, và với mỗi bài toán, chúng ta phải “nhìn ngược lại” bài toán trước đó, nên cách làm này gọi là quy hoạch động kiểu “ngược”.
Phương pháp quy hoạch động ngược này được sử dụng rộng rãi, vì nó khá tương ứng với suy nghĩ tự nhiên của chúng ta. Chúng ta đọc đề bài, suy nghĩ cách giải cho nó. Cách giải đó yêu cầu phải giải những bài toán nhỏ hơn, như kiểu làm toán ngày phải chứng minh các bổ đề vậy. Chúng ta tiếp tục suy nghĩ cho những bài toán con này, rồi tổng hợp để tìm ra lời giải cho bài toán lớn. Quá trình cứ tiếp tục như vậy, và quy hoạch động kiểu “ngược” này đang được xây dựng đúng như vậy.
Ngoài ra, về mặt lập trình, kiểu quy hoạch động này có mối quan hệ tương đối gần gũi với đệ quy. Một bài toán lớn được giải dựa vào các bài toán con tương tự nhau (và tương tự bài toán lớn) thì việc áp dụng đệ quy có thể là một phương pháp dễ dàng để code. Vì vậy, nhiều trường hợp, có thể coi quy hoạch động là một cách để tối ưu phương pháp đệ quy để giải một bài toán.
Ngoài kiểu quy hoạch động ngược này, có một kiểu quy hoạch động “xuôi”. Tuy không phổ biến, kiểu quy hoạch động xuôi cũng khá khó áp dụng, nhưng quy hoạch động “xuôi” mang đến cho chúng ta nhiều tiện lợi. Kiểu xuôi này cũng cần duyệt qua các bài toán con từ nhỏ đến lớn, nhưng với mỗi bài toán con, chúng ta tính toán kết quả và từ đó tìm cách thực hiện một số phép tính để giải bài toán lớn hơn. Nghĩa là, với mỗi bài toán con, chúng ta sẽ nhìn về phía trước để xem phải giải bài toán tiếp theo như thế này từ bài toán hiện tại.
Phương pháp này khó áp dụng hơn phương pháp ngược kia, và cũng không phải bài toán nào cũng áp dụng được. Với mỗi bài toán, việc xác định bước tiếp theo tương đối khó khăn, thậm chí việc kiểm tra tính đúng sai của phương pháp cũng không hề dễ dàng.
Như chúng ta đã thấy ở những phần trước, thông thường, mỗi bài toán cần phải giải bằng cách tổng hợp kết quả từ một vài bài toán con trước đó. Vì vậy, cách quy hoạch động xuôi này chỉ sử dụng một bài toán con để tính toán trước bài toán tiếp theo sẽ chỉ cho ra một phần của kết quả chứ không phải kết quả cuối cùng. Vì vậy, để thực hiện quy hoạch động xuôi, việc điền sẵn một mảng các giá trị trung tính là điều bắt buộc (sau đó chúng ta sẽ cộng dồn kết quả vào mỗi khi giải được một bài toán con mới).
Mình lấy vị với bài toán xâu con chung dài nhất. Với bài toán này, chúng ta có thể chọn giá trị trung tính là một số âm. Chúng ta sẽ tìm cách quy hoạch động xuôi như sau:
  • dp(0,0) = 0 là bài toán với hai xâu rỗng
  • Với mỗi bài toán dp(i, j) chúng ta sẽ tìm cách tính toán kết quả cho các bài toán lớn hơn. Lúc này, có 3 hướng phát triển tiếp:
    1. Lấy thêm một ký tự từ xâu thứ nhất => Kết quả không thay đổi.
    2. Lấy thêm một ký tự từ xâu thứ hai => Kết quả cũng không thay đổi.
    3. Nếu ký tự tiếp theo của cả hai xâu giống nhau => Lấy tự từ này và độ dài xâu con chung tăng lên 1.
Dưới đây là code cho bài toán này:
n1, n2 = map(int, input().split())
s1, s2 = input().split()
s1 += '\0x00'
s2 += '\0x00'

# Điền sẵn giá trị trung tính
dp = [[-1] * (n1 + 2) for _ in range(n2 + 2)]
dp[0][0] = 0

for i in range(n1 + 1):
    for j in range(n2 + 1):
        tres = dp[i][j]
        # Phát triển theo hướng thứ nhất
        if dp[i + 1][j] < tres:
            dp[i + 1][j] = tres
        # Phát triển theo hướng thứ hai
        if dp[i][j + 1] < tres:
            dp[i][j + 1] = tres
        # Phát triển theo hướng thứ ba
        if s1[i] == s2[j] and dp[i + 1][j + 1] < tres + 1:
            dp[i + 1][j + 1] = tres + 1

print(dp[n1][n2])

Kết luận

Hy vọng qua bài viết này, mình đã trình bày được phần nào về phương pháp quy hoạch động. Về cơ bản, với mọi bài toán quy hoạch động, chúng ta có thể xây dựng các bài toán con gối nhau với cấu trúc con tối ưu là 90% công việc đã hoàn thành.
Tuy nhiên, cũng cần hiểu rằng, mặc dù quy hoạch động là một thuật toán thần thánh, nó có thể giải được rất nhiều bài toán, nhưng nó không phải là chìa khóa vạn năng. Có một điều rất hiển nhiên: phương pháp tốt nhất để giải quyết mọi bài toán trong tin học là biết sử dụng và phối hợp uyển chuyển nhiều thuật toán, chúng ta không nên phát cuồng một thuật toán và cũng không nên coi thường bất cứ một thuật toán nào.

Thứ Sáu, 31 tháng 8, 2018











0.5 Cài đặt IDE để lập trình C++

C++ cơ bản

Cấu trúc rẽ nhánh

Cấu trúc vòng lặp

Nâng cao về biến, kiểu dữ liệu

Kiểu dữ liệu mảng

Kiểu chuỗi kí tự

Cơ bản về Function

Con trỏ

Kiểu dữ liệu tự định nghĩa

Nhập, xuất, streams (Input & Output)

Standard Template Library

Smart pointer

12.0 unique_ptr
12.1 shared_ptr

Quản lý mã nguồn

13.0 Từ khóa static
13.1 Viết chương trình với nhiều file
13.2 Tạo và sử dụng thư viện liên kết tĩnh
13.3 Tạo và sử dụng thư viện liên kết động

Một số feature trong C++11, C++14

14.0 Function template
14.1 Lambda expression
14.2 Xử lý ngoại lệ

Link liên quan

  1. C++ toàn tập
  2. Khóa học C++
  3. [C++] - Tổng quan nội dung ngôn ngữ lập trình C++.
  4. C++: Cách đọc và tách dữ liệu theo định dạng cho trước
  5. Bài tập C++ (có lời giải mẫu)
  6. Bài tập C++ có giải (code mẫu) về biến, kiểu dữ liệu và toán tử
  7. 97 bài tập C++ cơ bản nâng cao có giải hay nhất

GitHub
  1.  Analysis of consolidation by prefabricated vertical drains
  2. Numerical Analysis Implementations
  3. Numeric library in C++ with basic methods
  4. Curated list of awesome software for numerical analysis
  5. A tool for automatic Montecarlo Arithmetic analysis
  6. Fast arbitrary dimension linear interpolation in C++
  7. Numeric C++ examples.
  8. A small C++ finite element method implementation
  9. FreeFem++ source code
  10. ForcePAD - Behavior of structures made easy

Thứ Năm, 9 tháng 8, 2018

Thiết kế sơ bộ móng cọc đứng và xiên chịu tải trọng ngang

Thiết kế sơ bộ móng cọc đứng, xiên chịu tải trọng ngang

Móng cọc chịu tải trọng ngang đồng thời lực dọc, mô men, thường bố trí cọc xiên để tăng khả năng chịu tải trọng. Tuy nhiên khi thiết kế sơ bộ chọn lựa số cọc đứng và cọc xiên thường dựa vào kinh nghiệm người thiết kế. Do vậy trong bài viết tác giả đề xuất cách tính đơn giản để thiết kế móng cọc

Phân tích kết cấu cầu sử dụng phần mềm Ansys

1. Giới thiệu chung
Phần mềm Ansys là một trong những phần mềm phân tính toán kết cấu cầu thông dụng đã được sử dụng bởi đơn vị tư vấn Kortes để tính toán kết cấu cầu Bính- cầu dây văng dầm thép liên hợp tại Hải Phòng.

Năm 2005, trong chương trình nghiên cứu ứng dụng cầu vòm ống thép nhồi bê tông, đơn vị tư vấn Trung Quốc cũng giới thiệu việc tính toán, phân tích tổng thể cũng như các chi tiết cục bộ bằng phần mềm Ansys/CivilFEM.

Phần mềm phân tích kết cấu cầu Ansys thường được ứng dụng để tính toán các kết cấu cầu phức tạp. Đôi khi kết quả phân tích phải được so sánh với thí nghiệm hiện trường để đảm bảo tính chính xác.

Ngoài ra, thí nghiệm hiện trường cũng rất cần thiết để đảm bảo các giả thiết trong tính toán thiết kế phù hợp với giả thiết.
thi-nghiem-phan-mem
thi-nghiem-hien-truong

Ansys được sử dụng để tính toán cầu có sườn thép lượn sóng. Ưu điểm của loại cầu này là nhẹ hơn cầu bê tông và sườn thép lượn sóng cũng có khả năng chống mất ổn định tốt hơn là sườn bản thép phẳng.

Kết cấu không gian từ các thanh thép tiết diện tròn, rỗng ngày càng phổ biến trong các bộ phận cơ bản của công trình cầu do tính thầm mĩ cao.

Hình ảnh dưới đây mô tả liên kết để kiểm tra ứng suất và phá hoại mỏi.
ket-cau-thong-dung
Với các kết cấu cầu kết hợp cả phần dầm bê tông và phần dầm thép, việc phân tích bài toán tiếp xúc giữa hai loại dầm rất cần được nghiên cứu kĩ bằng các phần mềm tính toán kết cấu cầu chuyên dụng với phương pháp phần tử hữu hạn 3D Solid

2. Kết luận
Ansys được đánh giá là phần mềm phân tích kết cấu cầu rất tốt để nghiên cứu các vấn đề về kết cấu mới, giải quyết được nhiều bài toán khó trong việc thiết kế công trình cầu.

(Sưu tầm)



Thứ Hai, 6 tháng 8, 2018

TCVN 10380:2014
ĐƯỜNG GIAO THÔNG NÔNG THÔN – YÊU CẦU THIẾT KẾ
                                               Rural roads - Specifications for design

Tiêu chuẩn quy định về tốc độ thiết kế và tải trọng trục tiêu chuẩn thiết kế theo bảng 3. sơ đồ tải trọng xe và đoàn xe thiết kế được qui định trong các tiêu chuẩn thiết kế cầu đều mang tính chất lý thuyết và giả định sao cho việc tính toán thiết kế được đơn giản nhưng các cây cầu được thiết kế và xây dựng phải đáp ứng yêu cầu lưu hành bình thường cho tất cả các phương tiện giao thông được chế tạo để làm nhiệm vụ vận tải đường bộ.

 Bảng 3. Tốc độ thiết kế và tải trọng trục tiêu chuẩn thiết kế
các công trình trên đường đối với các cấp đường GTNT
Cấp kỹ thuật của đường
Tốc độ xe chạy thiết kế, Km/h
Tải trọng trục xe thiết kế, Kg
Kiểm toán đối với xe vượt tải có tải trọng trục, Kg
A
30 (20)
6000
10000
B
20 (15)
2500
6000
C
15 (10)
2500
6000
D
-
-
-
CHÚ THÍCH: Trị số trong ngoặc (20) áp dụng đối với địa hình miền núi (độ dốc ngang địa hình > 30%).

Thứ Năm, 12 tháng 10, 2017

Nghiên cứu phương pháp tính toán ổn định kết cấu công trình ngầm


I. Đặt vấn đề
Xây dựng công trình ngầm ngày càng phát triển, nên việc đi sâu nghiên cứu lý thuyết về áp lực và ổn định kết cấu công trình ngầm, lý thuyết thiết kế kết cấu gia cố công trình ngầm được rất nhiều nhà khoa học trên thế giới  quan tâm. Từ góc độ cơ học, kết cấu công trình ngầm mất tính ổn định  là do ứng suất vượt quá cường độ ứng suất cho phép, tạo ra vùng đứt gãy và trượt liên tục.
Hai mươi năm đầu của thế kỷ XX, ổn định kết cấu công trình ngầm chủ yếu dựa vào lý thuyết áp lực cổ điển, lý thuyết này cho rằng: lực tác dụng lên kết cấu gia có chủ yếu là trọng lượng của đất đá xung quanh công trình. Do công trình ngầm ngày càng được thi công sâu trong lòng đất nên lý thuyết trên không còn phù hợp và đã xuất hiện lý thuyết áp lực tự do, lý thuyết này cho rằng sự sụt lở của vùng địa chất xung quanh là do phát sinh áp lực địa tầng. Những năm cuối thế kỷ XX, các nhà khoa học bắt đầu áp dụng lý thuyết tính đàn hồi dẻo để nghiên cứu và khi tính toán đã xét đến sự tương tác giữa kết cấu gia cố và địa chất xung quanh công trình, đồng thời xét đến sự tương tác giữa kết cấu gia cố và địa chất xung quanh công trình, đồng thời xét đến khe nứt, đứt gãy của tầng địa chất.
II. Các phương pháp tính toán ổn định kết cấu công trình ngầm
Căn cứ vào phân tích lý thuyết và mô hình toán học các phương pháp phân tích tính toán ổn định kết cấu công trình ngầm có thể chia thành các loại sau:
1. Phương pháp phân tích
 Phương pháp phân tích sử dụng hàm số phức để tìm ra nghiệm đàn hồi ứng suất- biến dạng của công trình ngầm. Nó có ưu điểm độ chính xác tương đối cao, tố độ phân tích nhanh, các tham số dễ xác định, đơn giản trong nghiên cứu vì có tính quy luật, đặc biệt chính xác trong việc tìm nghiệm đối với mặt cắt hình tròn. Nhược điểm là chỉ thích hợp với phân tích  ứng suất- biến dạng công trình sâu, còn đối với công trình nông thì việc xử lý số học đối với sự ảnh hưởng của các lớp đất đá và ngoại lực bề mặt tương đối khó.
2. Phương pháp phân tích trị số
- Phương pháp phần tử hữu hạn: Phương pháp phần tử hữu hạn được hình thành từ những năm 40 của thế kỷ XX, đến nay đã trở nên hoàn thiện và được sử dụng rộng rãi để tìm nghiệm cho bài toán có tính đàn hồi, tính đàn- dẻo, tính dính - dẻo. Ưu điểm của nó là có xét đến tính không liên tục, không đồng nhất của kết cấu địa tầng, có thể giải các bài toán có biên phức tạp, tính ra được trị số của ứng suất- biến dạng và phân bố của chúng, dựa vào quy luật phân bố để phân tích cơ chế phá hoại kết cấu công trình ngầm
- Phương pháp phân tích biến dạng không liên tục: là phương pháp phân tích trị số mới được phát triển dựa trên cơ sở không liên tục của môi trường địa chất ngoài vỏ công trình. Đây là phương pháp được tiến hành song song với phương pháp phần tử hữu hạn, điểm khác biệt nằm ở chỗ, phương pháp này có thể tính toán được lực tĩnh và lực động của chuyển vị lớn như xoay, đứt gãy, trượt không liên tục.. Ngoài ra mô hình này cũng có khả năng ứng dụng lớn trên phương diện mô phỏng thực quá trình biến dạng cơ học không liên tục của kết cấu địa tầng.
- Lý thuyết ‘Key bock”: lý thuyết ‘Key bock” do Goodman và Shi gen Hua đưa ra năm 1985 dùng để phân tích ổn định công trình. Điểm mấu chốt của lý thuyết này cho rằng kết cấu mặt cắt địa chất các tầng đá cứng, nửa cứng rất phức tạp vì khối đá được hình thành từ nhiều nguyên nhân khác nhau. Nội dung của lý thuyết ‘Key bock” là chuyển các đặc tính khác nhau cảu các mặt cắt địa chất cũng như các đứt gãy (mặt kết cấu) của khói đá thành đặc tính chung đồng nhất, dựa trên nguyên lý hình học tô pô, phương pháp hình chiếu lập thể và sử dụng phân tích véctơ để tạo ra tất cả loại hình cấu tạo khối có thể có, sau đó dựa trên nguyên lý cơ học để tiến hành phân tích ổn định của các khối dựa trên khối chủ yếu đã chọn. Tuy nhiên, do không thể biết chính xác về phân bố hình thái mặt kết cấu của khối đá, hơn nữa độ biến động của chúng tương đối lớn, mặt kết cấu cũng không hoàn toàn là mặt phẳng, vì thế khi tính toán chỉ cần một sơ suất nhỏ có thể sẽ cho ra kết quả không chính xác.
- Phương pháp phần tử phân tán: phương pháp này được Cundall đưa ra năm 1971, được ứng dụng nhiều trong tính toán công trình ngầm ngày nay. Nội dung cơ bản của phương pháp này là: các khối đá trong địa tầng có sự tác dụng lẫn nhau, đồng thời chịu ảnh hưởng của phương trình chuyển động phản lực- gia tăng tốc độ chuyển vị  và phương trình vật lý đặc trưng của lực- chuyển vị, thông qua sư thay đổi để tìm nghiệm hiển thị quá trình hoạt động của khối đá. Một giả thiết cơ bản trong phương pháp là khi khối chuyển động thì động năng sẽ chuyển hoá thành nhiệt và tiêu hao đi, do đó khi tính toán ngay cả vấn đề lực tĩnh cũng phải chuyển đổi dạng lực dính giảm dần để cho hệ thống đạt đến sự cân bằng, chuyển động của các khối dần ổn định. Phương pháp này chủ yếu dùng để phân tích tác dụng tương hỗ của khối đá nứt nẻ và neo gia cố. Nguyên lý tính toán của phương pháp phần tử phân tán đơn giản, nhưng quá trình thực hiện trên máy tính lại vô cùng phức tạp, liên quan đến nhiều vấn đề, đặc biệt việc xác định tham số tính toán hệ số giao động  giữa các khối theo thời gian có tính ngẫu nhiên
- Phương pháp phần tử khối: phương pháp này do Ren Qing Wen đưa ra, lấy chuyển vị vật rắn của các phần tử khối làm ẩn số, căn cứ vào quan hệ kết cấu vật liệu đan xen với điều kiện cân bằng biến dạng dưới tác dụng của ứng suất mặt hở và ngoại lực tác dụng lên phần tử khối. Áp dụng nguyên lý biến phân lập ra phương pháp điều khiển, dùng để xác định trạng tháí ứng suất và chuyển vị của khối. Phương pháp này có thể giải bài toán môi trường địa chất không liên tục, giảm được lượng ẩn số, đọ chính xác cao, tốc độ trong tính toán được nâng lên, đặc biệt thích hợp cho việc phân tích ổn định và tính ứng suất- biến dạng của khối đá nhiều nứt gãy.
- FLAC (Fast Lagrangian Anlysis of Continua): cundall căn cứ vào nguyên lý của phương pháp sai phân hữu hạn để đưa ra phương pháp phân tích trị số FLAC. Các tác giả Diederich và Kaiser đưa ra mô hình để phân tích ảnh hưởng áp lực nước trong địa tầng đối với tính ổn định của công trình. Phương pháp này có thể giải bài toán xét đến đặc trưng biến dạng lớn và không liên tục của khối đất đá một cách hoàn thiện, tính toán nhanh hơn. Nhưng nhược điểm phương pháp này phân chia mạng phân tử, biên tính toán rất hợp lý.
- Phương pháp phần tử biên: hay còn gọi là phương pháp phương trình phân tích phân biên, được học giả Bribbia người Anh sáng lập từ những năm 60 của thế kỷ XX. Ưu điểm của nó là tiến hành phân ly một số phần tử trên biên của vùng tính toán, như thế sẽ bớt được một chiều trong không gian đa chiều tính toán, kết quả tính toán có độ chính xác khá cao, tính được ứng suất và chuyển vị một cách rõ ràng, việc chia lưới phân tử đơn giản, yêu cầu dung lượng bộ nhớ máy tính thấp và công việc tính toán ít; đây là một phương pháp được ứng dụng rất nhiều trong phần mềm phân tích kết cấu công trình. Nhưng phương pháp này tỏ ra khó thích hợp với các bài toán biến hệ số và phi tuyến tính, những bài toán có biên phức tạp, hơn nữa ứng dụng nó còn phụ thuộc vào việc giải phương trình có nghiệm cơ bản hay không.
- Phương pháp phân tích  phần tử khối- lò xo: năm 1987 Kawai áp dụng đơn giản hoá khối rắn để mô phỏng mô hình trị số phân tử lò xo thể rắn trong môi trường không liên tục. Mô hình này lấy chuyển vị thể rắn của phân tử trung tâm làm ẩn số chưa biết, chri tính đến quan hệ kết cấu và biến dạng cân đối của mặt phân tử  tiếp giáp để giải phương trình điều khiển xác định ứng suất và chuyển vị tương đối của mặt tiếp giáp. Mô hình này còn có ưu điểm khi phân tích tính ổn định của nứt gãy trong địa tầng, phản ánh được quy luật chuyển động và biến dạng không liên tục của kết cấu công trình.
3. Phương pháp loại suy địa chất công trình
Đây là một trong những phương pháp quan trọng để đánh giá ổn định kết cấu công trình ngầm, đặc biệt càng phát huy tác dụng trong giai đoạn nghiên cứu khả thi với tài liệu đo đạc ít… Cái mới của phương pháp này là đi từ định tính đến định lượng, từ đơn chỉ tiêu phát triển thành đa chỉ tiêu, ứng dụng phương pháp đánh giá tổng hợp của lý thuyết toán học mơ hồ, lý thuyết hệ thống màu xám, lý thuyết mạng thần kinh, lý thuyết phân hình… để phân loại kết cấu công trình ngầm ngày càng hợp lý hoá, khoa học hoá .
4. Phương pháp thực nghiệm mô hình
Nghiên cứu ổn định kết cấu công trình ngầm luôn đi cùng với thực nghiệm mo hình, tính tương tự giữa mô hình và công trình thực tế là vấn đề quan trọng để lựa chọn mô hình thực nghiệm. Do còn tồn tại nhiều thiếu sót và chưa đầy đủ trong phân tích lý luận, các nhà khoa học trên thế giới đã tiến hành rất nhiều nghiên cứu thực nghiêm mô hình và rút ra nhiều kết luận có ý nghĩa. Phương pháp này thường được dùng nhiều trong những công trình phức tạp và trong trường hợp phương pháp thực nghiệm hiện trường không thể tiến hành được.
5. Phương pháp phân tích hệ thống
Phân tích ổn định kết cấu công trình ngầm thông thường phân chia một cách chi tiết các yếu tố địa chất, kết cấu vỏ, quá trình gia cố trong khi đào hầm…thông qua phân tích lý thuyết dựng nên mô hình toán. Hệ thống xây dựng công trình ngầm có đặc điểm nhiều lớp, nhiều yếu tố, kết cấu của nó vô cùng phức tạp, đồng thời tổng thể hệ thống đường hầm được tạo thành từ các bộ phận riêng biệt, cho nên, phân tích cơ học đường hầm hoàn toàn phải có đầy đủ hệ thống khoa học trong nghiên cứu đặc trưng của “hệ thống”. Vì thế, phương pháp này chính là mô phỏng toán học sự tác dụng tương hỗ hệ thống địa chất xung quanh và vỏ công trình.
III. Các vấn đề tồn tại và những định hướng giải quyết
Thông qua việc phân tích hệ thống của các phương pháp trên, chúng ta có thể thấy các phương pháp phân tích hiện nay còn tồn tại một số nhược điểm như sau:
- Các phương pháp trên đều chưa giải quyết được trọn vẹn các vấn đề thực tiễn của công trình, cần tiếp tục tiến hành đi sâu nghiên cứu phương pháp mô phỏng chân thực, tham só tính toán, mô hình lý thuyết. Kết cấu công trình ngầm mất ổn định là một quá trình tương đối phức tạp, thông thường có chuyển vị lớn, tính không liên tục, không đồng đều của biến dạng là một vấn đề khoa học mang tính phi tuyến cao. Do đó, cần phải dựa vào cách giải quyết các bài toán phi tuyến tính đương đại để tiến hành dự đoán và khống chế đối với chuyển động cơ học của nó.
- Phương pháp phân loại suy địa chất công trình  được áp dụng một cách rộng rãi, nhưng hiện nay trên thế giới có hàng trăm tiêu chuẩn phân loại đánh giá tính ổn định của kết cấu công trình ngầm do đó, cần phải tiếp tục nghiên cứu và hoàn thiện việc đánh giá định lượng.
- Phương pháp phân tích là một phương pháp hữu hiệu, thế những xét theo tình trạng hiện nay cho thấy mức độ nghiên cứu vẫn chưa sâu, thành quả nghiên cứu cũng tương đối ít. Các thành quả nghiên cứu hiện nay thông thường đều giả thiết địa chất là môi trường liên tục, đồng nhất, đẳng hướng, trạng thái ứng suất công trình ngầm sau khi thi công được tính theo đàn hồi và đàn dẻo, căn cứ vào lý thuyết cường độ tiến hành đánh giá tính ổn định công trình ngầm. Tuy rằng đã có một số nhà nghiên cứu lập ra được phương pháp phân tích chuyển vị tương đối dưới các trạng thái ứng suất khác nhau của công trình ngầm hình tròn, nhưng thiết lập quan hệ giữa giải bài toán chuyển vị tương đối và tính ổn định của kết cấu công trình ngầm chưa được nghiên cứu đầy đủ.
- Hiện nay, khi dùng phương pháp trị số để đánh giá ổn định kết cấu công trình ngầm chủ yếu vẫn dùng phương pháp mô phỏng trị số phần tử hữu hạn, điều này đòi hỏi chúng ta phải đẩy mạnh công tác nghiên cứu các phương pháp khác để có sự so sánh và đánh giá tốt hơn.
- Ngoài ra, rất khó xác định tiêu chuẩn để đánh giá sự mất ổn định, nên trong ứng dụng thực tế chúng ta phải kết hợp với hiện trạng công trình để đưa ra các căn cứ đánh giá mất ổn định một cách hợp lý nhất.
IV. Kết luận
- Các phương pháp trên đều chưa giải quyết được trọn vẹn các vấn đề thực tiễn của công trình, cần tiếp tục tiến hành đi sâu nghiên cứu.
- Do các vùng địa chất xây dựng công trình ngầm cũng như yêu cầu sử dụng thực tế khác nhau nên cần lựa chọn một phương pháp tương ứng để tính toán kết hợp sử dụng tiêu chuẩn thích hợp để đánh giá độ ổn định của kết cấu công trình ngầm.
- Sự phát triển của mô hình toán và cơ học hiện đại là cơ sở tốt cho chúng ta mở rộng nghiên cứu. Cần phải xuất phát từ công trình ngầm thực tế, với khái niệm hệ thống làm chủ đạo, dựa vào sự kiểm nghiệm và phản hồi của các tài liệu quan trắc mô hình gốc, kết hợp phân tích lý thuyết với phân tích kinh nghiệm, tiếp tục đẩy mạnh nghiên cứu lý thuyết cơ sở của mô hình cơ học phân tích ngược chuyển vị và mô hình phân tích trị số.

 Nguồn: Tạp chí Cầu đường Việt Nam, số 10/2009