Sau một thời gian dài (2017-2023) chúng ta cùng nhau học và thi lập trình trên oj.husc.edu.vn.
Một web chấm bài dựa trên nền tảng Domjudge được hiệu chỉnh lại hầu hết các chức năng cho phù hợp với nhu cầu sử dụng.
Chúng ta đã có được nhiều lợi ích cho việc học và thi lập trình nhất là các đội tuyển ICPC và OLP của Khoa.
Thuận theo xu thế chung và tiện hơn cho việc giao tiếp cộng đồng, nay chúng tôi xây dựng lại web chấm bài và đổi tên là coder.husc.edu.vn dựa trên nền tảng DMOJ. Các bài tập dạng cơ bản sẽ được đặt tên là Nhập môn lập trình dành cho sinh viên khoa Công nghệ Thông tin Đại học Khoa học Huế. Phần bài tập còn lại sẽ phân bố theo đúng chuẩn của các trang web lập trình, tức là gán nhãn cho từng kiểu bài tập và thể loại của chúng. Và để nghiêm túc các bài toán sẽ gán nhãn nguồn tham chiếu nếu sử dụng chúng.
Hy vọng các bạn sẽ có trải nghiệm mới, hiện nay chúng tôi đã xây dựng xong UI cho có tính riêng biệt hơn và đẹp hơn.
Riêng các bạn hãy thực hiện phương châm sau:
Bài toán sử dụng kĩ thuật ghép hình rất hay gặp trong các bài Dynamic Programming hình học. Ở cuối bài viết, mình giới thiệu
một số bài toán tương tự sử dụng kĩ thuật này.
Cách tiếp cận 1: Duyệt qua tất cả tập con , tìm diện tích bao lồi và cập nhật kết quả. Lưu ý rằng . Độ phức
tạp . Với giới hạn , thuật toán trên không tối ưu. Trước khi đến cách tiếp cận tối ưu hơn, ta có
một số nhận xét như sau:
Nhận xét 1: Với các đỉnh tọa độ nguyên, diện tích đa giác (kể cả đa giác không lồi) có giá trị nguyên khi và chỉ khi hai lần
diện tích đa giác là số chẵn (dễ dàng chứng minh với Pick’s theorem hoặc Shoelace formula).
Nhận xét 2: Với tập điểm , gọi là tập các điểm nằm trên bao lồi của và là tập các điểm nằm bên trong bao
lồi của . Gọi là tập con của và là tập các điểm nằm trên bao lồi của . Khi đó số
lượng tập thỏa mãn (bao lồi của và trùng nhau) là .
Ví dụ: Hình có tập thỏa mãn .
Nhận xét 3: Quan sát ở hình , gọi điểm có hoành độ nhỏ nhất là điểm trái nhất và điểm có hoành độ lớn nhất
là điểm phải nhất . Từ đoạn thẳng nối hai điểm trên, ta có thể chia đa giác lồi thành hai phần:
phần nửa trên (tạm gọi là Upper) gồm các điểm nằm phía trên đường nối và phần nửa dưới (Lower) gồm các điểm nằm phía dưới đường nối.
Khi đó diện tích đa giác lồi bằng tổng diện tích hai phần nửa trên và nửa dưới: Convex_Area = Upper_Area + Lower_Area.
Cách tiếp cận 2: Quy hoạch động với kĩ thuật ghép hình:
Sắp xếp điểm theo thứ tự tăng dần hoành độ , nếu có cùng hoành độ , sắp xếp theo thứ tự tăng dần tung độ .
Duyệt qua với mọi đỉnh , ta đếm số lượng tập điểm thỏa mãn yêu cầu đề bài và bao lồi của nó nhận điểm là
điểm trái nhất (có hoành độ nhỏ nhất) với tư tưởng quy hoạch động như sau:
Gọi dp[z][i][j][k] với là số lượng tập thỏa mãn đồng
thời các yêu cầu sau:
Bao lồi của nó nhận điểm là điểm trái nhất và là điểm phải nhất.
nếu là một Upper (mọi điểm thuộc tập nằm phía trên đường nối và ) và nếu là một Lower
(mọi điểm thuộc tập nằm phía dưới đường nối và ).
Bao lồi của nó nhận điểm là điểm liền kề đứng trước điểm (khi duyệt bao lồi từ đến , hai điểm
cuối cùng được duyệt tương ứng là và ).
Phần dư khi chia hai lần diện tích bao lồi của cho bằng ( nếu hai lần diện tích là số chẵn và
nếu hai lần diện tích là số lẻ).
Cách tính dp[z][i][j][k] được mình họa ở hình sau:
Tư tưởng ghép hình: Với đa giác lồi đã được tính toán, ta tiến hành ghép tam giác
với để có được đa giác mới . Từ đó, ta có công thức quy hoạch động như sau:
Trong đó:
parity[a][b][c] = 0 nếu hai lần diện tích tam giác là số chẵn, ngược lại parity[a][b][c] = 1.
cnt[a][b][c] là số lượng điểm nằm bên trong tam giác .
nếu điểm theo thứ tự chiều kim đồng hồ và nếu điểm theo
thứ tự ngược chiều kim đồng hồ.
Các giá trị parity[a][b][c] và cnt[a][b][c] có thể tính toán trước trong .
Một số lưu ý trong công thức QHĐ trên:
Việc ta phải nhân với đã được đề cập trong nhận xét 2.
Giới hạn của là chứ không phải . Việc do Upper hoặc Lower có thể là một
đoạn thẳng (đa giác chỉ có đỉnh). Minh họa ở hình:
Từ nhận xét 3, ta có kết quả của bài toán được tính theo công thức:
Lưu ý rằng ở đáp án, ta phải trừ đi số lượng tập với do ta đã đề cập về vấn đề Upper hoặc Lower có thể là
một đoạn thẳng ở trên.
Mở rộng: Kĩ thuật ghép hình như trên được dùng ở hầu hết các bài toán QHĐ hình học, đặc biệt là hình học bao lồi.
Để luyện tập kĩ thuật này, các bạn có thể tham khảo một số bài toán:
Empty Convex Polygons - vjudge : Bài toán theo mình khá hay vì thể hiện rõ việc sử dụng kĩ thuật “ghép hình” để tìm đa giác lồi rỗng (đa giác không có điểm nằm trong) có diện tích lớn nhất.
Polygon – codeforces : Mặc dù ở mức 3000* nhưng với việc áp dụng kĩ thuật “ghép hình” thì tư tưởng để giải bài toán này không quá khó.
Tóm tắt đề bài: Cho đồ thị vô hướng liên thông G = (V, E), |V| = n, |E| = m. Đồ thị G có ít nhất một cạnh cầu. Cho hai đỉnh s và t, tìm thời gian nhỏ nhất để đi từ (s, t) tới (u, v) với (u, v) là hai đỉnh của một cạnh cầu bất kỳ trong G.
Ban đầu, ta sử dụng thuật toán Dijkstra để tính đường đi ngắn nhất từ đỉnh s và đỉnh t tới các đỉnh còn lại trong G.
Sau đó, ta đi tìm tất cả các cạnh (u, v) là cầu của đồ thị G. Thời gian nhỏ nhất để đi từ (s, t) tới (u, v) được tính theo công thức sau :
t1 = max(dists[u], distt[v])
t2 = max(dists[v], distt[u])
time(u, v) = min(t1, t2)
Đáp án là min(time(u, v)) với (u, v) là cạnh cầu của G
Tóm tắt đề bài: Cho đồ thị vô hướng (cây) . Tìm đường đi (mỗi đỉnh đi qua đúng một lần) có tích số nhỏ nhất, với tích số của đường đi được tính bằng tích trọng số các nút của đường đi chia số nút trên đường đi.
Ta sẽ tạm gọi các đỉnh có trọng số là đỉnh , và các đỉnh có trọng số lớn hơn là đỉnh .
Ta nhận thấy rằng đường đi tối ưu nhất chỉ chứa tối đa đỉnh .
Nếu không có đỉnh , đường đi có tích số nhỏ nhất cần tìm chỉ có một đỉnh với giá trị trọng số nhỏ nhất.
Ngược lại, đường đi cần tìm thuộc vào một trong hai dạng theo thứ tự duyệt:
Dạng : Đường đi bao gồm đỉnh , tích số .
Dạng : Đường đi bao gồm đỉnh , một đỉnh và đỉnh , tích số
Ta dễ dàng suy ra rằng để tìm được phương án tối ưu, ta cần đi qua càng nhiều đỉnh càng tốt.
Xét ví dụ sau như hình vẽ :
Với dạng thứ nhất, ta chỉ đi qua các đỉnh , vì vậy ta có thể biến đổi bằng cách tạm thời xóa đi những cạnh nối hoặc .
Ta có hình vẽ .
Do có đúng cạnh nên bị chia thành các thành phần liên thông (cây con), với ví dụ trên ta sẽ tạm gọi các thành phần
liên thông (cây con) chỉ chứa đỉnh là: cây , cây và cây (tương ứng với số đỉnh).
Lúc này, đường đi chỉ có thể thuộc một trong các cây con. Với mỗi cây con, số đỉnh nhiều nhất có thể đi qua chính là đường kính
của cây đó (Các bạn có thể tham khảo định nghĩa, tính chất và thuật toán tìm đường kính tại đây).
Ta dễ dàng có được bằng độ dài đường kính lớn nhất trong số các cây con. Trong ví dụ trên, là đường kính của cây .
Sau khi có được thuật toán cho dạng thứ nhất, ta có thể đưa ra được ý tưởng cho dạng đường đi thứ hai:
Kết nối hai cây con thông qua một "khớp nối" là đỉnh .
Mỗi đỉnh là "khớp nối" nếu có cạnh nối với đỉnh (). Vì có số cạnh nên đỉnh
kết nối cây con. Đường đi dạng thứ hai bao gồm đỉnh và hai nhánh thuộc hai trong số cây con.
Trong ví dụ trên, đường đi dạng thứ hai tối ưu nhất có tích số , trong đó có hai đỉnh thuộc cây con
và ba đỉnh thuộc cây con .
Vậy tóm lại, ta cần làm như sau:
Tìm các cây con chỉ chứa đỉnh .
Tìm đường kính lớn nhất trong số các cây con để tối ưu đường đi dạng thứ nhất.
Duyệt qua với mỗi đỉnh , tìm hai nhánh có độ dài lớn nhất để tối ưu đường đi dạng thứ hai.
Kết quả là giá trị tích số nhỏ nhất trong hai dạng đường đi.
Chúng ta cần tìm cực trị của hàm theo giới hạn (kết quả đạt tối đa khi tổng bằng ) bằng cách sử dụng phương pháp nhân tử Lagrange.
Đặt là hàm Lagrange.
Tiếp theo, lấy đạo hàm riêng của theo và . Đặt các đạo hàm riêng này bằng sẽ cho chúng ta các giá trị của và tối ưu hóa theo ràng buộc .
Từ ba phương trình , ta có ba vế bằng nhau (cùng bằng ):
Chia tất cả các vế cho , ta được mối liên hệ
Dựa vào mối liên hệ và phương trình ở trên ta có thể thấy tích các lũy thừa đạt giá trị tối đa khi tỉ lệ với .
Thu được công thức tính cho từng giá trị là:
x = S * a / (a + b + c), y = S * b / (a + b + c), z = S * c / (a + b + c);
Xét riêng trường hợp đặc biệt thì kết quả công thức trên trả về giá trị vô định nan do có mẫu số bằng . Cần phải xử lý riêng bằng việc đặt , cho kết quả với mọi ( được quy ước có giá trị trong đại số)
Tóm tắt đề bài: Cho món đồ, mỗi món có khối lượng và giá trị . Có cái túi, mỗi cái túi chứa được tối đa một đồ vật với khối lượng tối đa . Tìm tổng giá trị lớn nhất có thể chọn để bỏ vào túi. Giới hạn: .
Nhận xét với dữ kiện “nhiều nhất một món cho một túi”, ta có thể giải quyết bài toán bằng thuật toán tham lam với các tư tưởng như sau:
Đầu tiên ta quan tâm đến những món đồ có giá trị từ cao đến thấp, xem với mỗi món đồ, có tồn tại cái túi nào có thể chứa được hay không.
Để tối ưu, với mỗi món đồ ta phải tìm cái túi có khối lượng nhỏ nhất chứa được.
Với tư tưởng trên, ta đơn giản tiến hành sắp xếp các món đồ theo giá trị tăng (hoặc giảm) của đại lượng . Ta tiến hành duyệt qua với mỗi món đồ, tiếp tục duyệt qua danh sách các cái túi, tìm cái túi có giá trị nhỏ nhất với điều kiện chứa được đồ vật đó . Tuy nhiên, độ phức tạp lúc này sẽ là , dẫn đến TLE.
Vì vây, ta cần tối ưu hóa việc tìm kiếm cái túi phù hợp. Một cách đơn giản nhất ta có thể sử dụng thuật toán tìm kiếm nhị phân: tiến hành sắp xếp cái túi theo khối lượng, sử dụng tìm kiếm nhị phân để tìm được cái túi nhỏ nhất với . Tuy nhiên, nếu sử dụng mảng, ta sẽ gặp khó trong việc xóa hoặc đánh dấu những cái túi đã được chọn (“nhiều nhất một món cho một túi”).
Các bạn có thể lưu danh sách cái túi với cấu trúc Set để thuận tiện trong việc sắp xếp cũng như xóa các phần tử.
Một lưu ý nhỏ trong việc cài đặt: vì CTDL Set lưu các giá trị khác nhau, vì vậy ta có thể gán nhãn cho các cái túi để biến chúng thành các “bộ giá trị khác nhau ”, cụ thể với mỗi cái túi sẽ có giá trị và nhãn là .
Độ phức tạp thuật toán: .
Các bạn có thể tham khảo cài đặt của mình ở mục hướng dẫn giải