Editorial THCS Contest 3

a519Hieu zipdang2004 - 31/03/2020

Có vẻ như THCS Contest 3 quá thách thức =)) Sau đây là solution cho 6 bài:

Bài A - SUBMERSION - tác giả: a519hanlcq

Chỉ cần đếm số lượng a[i] sao cho a[i] > m và a[i] chia hết m.

Bài B - OANQUANE - Tác giả: A519QuyBacktracking
Đây là một bài tương đối trực quan, bạn chỉ đơn giản là duyệt qua tất cả cá khả năng: với mỗi ô hàng trên có chứa sỏi, các bạn chỉ cần cập nhật biến kết quả res theo 2 giá trị là số sỏi ăn được khi rải đống sỏi này theo chiều kim đồng hồ và ngược chiều kim đồng hồ. Để tiện cho việc cài đặt, các bạn dùng mảng a[12] ứng với các ô của bàn cờ như sau: 

a[0] a[1] a[2] a[3] a[4] a[5]  
  a[11] a[10] a[9] a[8] a[7] a[6]

Giả sử các bạn đang ở tại ô thứ i, ô tiếp theo theo chiều:

  • kim đồng hồ: (i+1) % 12
  • ngược chiều kim đồng hồ (i-1 + 12) % 12 hoặc (i+11)%12

 Để việc cài đặt được nhanh gọn, tránh trùng lặp (4 hoặc 2 lần), các bạn nên viết hàm int pick(int pos, int dir) là số sỏi Hân sẽ ăn được nếu bốc đống sỏi tại vị trí pos (1 <= pos <= 5) rồi đi theo chiều dir (chọn 1 nếu thuận và 11 nếu nghịch, khi đó thao tác tìm ô tiếp theo : nxt = (pos+dir)%12). Trong quá trình bốc sỏi và rải sang các ô, việc thực hiện các thay đổi các bạn đều thực hiện trên một mảng tạm là b, chứ không thay đổi giá trị mảng a, mảng b được gán bằng a mỗi khi bạn gọi hàm pick.

Bài C - NewGame - tác giả: a519hanlcq

Gọi mảng c là chênh lệch sức mạnh của từng cặp người và quái vật. Sau đó ta sort lại mảng c và với mỗi c[i], ta tìm j xa nhất mà c[i] + c[j] > 0. Ta có thể tìm bằng chặt nhị phân hoặc two pointer, ...

ĐPT: O(nlogn).

Bài D + F - DIVONE/DIVTWO - tác giả: a519Hieuzipdang2004

Có lẽ một số bạn quên mất cách chia giấy bút :(

Ở bài đầu tiên - DIVTWO, các bạn sẽ viết thuật toán mô phỏng cách chia này. Sau đây là ví dụ:


Phép chia trên cho ra kết quả là 1.(6)8Lưu ý: 158/78 (tương ứng với 1310/710) khác với 1510/710 nha.

Gọi s[i] là chữ số thứ i sau dấu thập phân của A/B, tương ứng với f(i) là số dư trong mỗi lần chia, với f(1) = A mod B, f(i) = (f(i - 1) * Z) mod B với i > 1 (nhân Z để mô phỏng việc thêm số 0 đằng sau). Ta sẽ lấy 3000(sub1) / 3000000(sub2) chữ số đầu sau dấu thập phân, tương ứng là f(1) tới f(3000) hoặc f(3000000). Việc của ta là tìm chu kỳ tuần hoàn trên f.

Ở sub1, ta sẽ tìm độ dài tuần hoàn và vị trí bắt đầu tuần hoàn bằng hai vòng for, một vòng for tìm độ dài tuần hoàn L, và một vòng tìm vị trí bắt đầu tuần hoàn. Từ vị trí bắt đầu tuần hoàn i, ta kiểm tra đến hết dãy f xem nó có tuần hoàn chu kỳ L không, nếu đúng thì ta lập tức kết luật bắt đầu tuần hoàn từ i chu kỳ L. Độ khó sub: hơn bài 3 HSG TP một tí + cài đặt phức tạp. Sub này có thenymphsofdelphi giải thành công.

Ở sub2, ta sẽ tìm chu kỳ tuần hoàn trên f bằng thuật toán Floyd's Hare and Tortoise (thuật toán Thỏ và Rùa). Đặt hai biến chạy: rùa = 1, thỏ = 2. Ta sẽ cho rùa đi 1 bước, thỏ đi hai bước cho tới khi f[rùa] = f[thỏ]. Khi đó, chu kỳ tuần hoàn chắc chắn sẽ chia hết cho x = rùa = (thỏ - rùa). Cho thỏ = 1, rùa = rùa + 1. Ta tìm vị trí bắt đầu tuần hoàn bằng cách dịch rùa và thỏ mỗi lần 1 đơn vị cho tới khi f[rùa] = f[thỏ], khi đó vị trí đầu tiên nằm ở con thỏ. Có được vị trí đầu tiên, giờ chỉ cần tìm chu kỳ tuần hoàn L sao cho f[thỏ] = f[thỏ + L]. Độ khó sub: dành cho THPT. Sub này rất tiếc là không ai giải được. 

Bài E - OANQUANH - Tác giả: A519QuyBacktracking
Bài này là phiên bản nâng cao hơn của bài B, đòi hỏi các bạn phải biết thuật toán quay lui (backtrack), các bạn có thể tìm đọc lý thuyết trong quyển DSAPTextbook (Giải thuật và lập trình) của thầy Lê Minh Hoàng.
Quay lui bản chất là thử và sai, ta thử từng trường hợp, sau đó lại lui lại (backtrack) để xem xét những trường hợp khác. Trong bài toán này, ta sẽ thử tất cả những nước đi Hân có thể đi trong n+1 lượt chơi của mình, với mỗi lượt đi (i<=n) của Hân, Quý luôn chơi lại theo thuật toán tham lam nên chúng ta chỉ cần làm giống như bài B.
Một vài lưu ý khi cài đặt:
+ Các bạn cần cài 2 hàm bốc sỏi riêng biệt, một hàm sẽ tác động đến mảng a - mảng chính của chúng ta, một hàm chỉ tác động đến một mảng phụ mà không thay đổi (với mục đích là xem trước để cân nhắc chọn ra nước đi ăn được nhiều sỏi nhất trong lượt đi của Quý)
+ Thêm một hàm int isEndYet(int player) để kiểm tra xem trò chơi đã kết thúc khi tới lượt của người chơi player chưa? (quy ước player = 0/1 ứng với Hân hay Quý là tùy thuộc vào bạn), hàm trả về -1 nếu trò chơi vẫn tiếp tục.
+ Kiểm tra sau n round và lượt đi thứ n+1 thì Hân có luôn thua hoặc luôn thắng? Mỗi lần trả về một lần kết thúc trò chơi (khi hàm ở trên trả về khác -1) thì bạn cập nhật vào mảng đếm cnt[x]++ để biểu thị có thêm 1 kết cuộc là người chơi x thắng trong tất cả các khả năng (outcomes). Mỗi một lần hàm đệ quy chạy quá n+1 round (tức là kết thúc) hay trả về một người chơi thắng/thua giữa chừng, bạn cũng cập nhật cntOutcomes++ (biến dùng để đếm những kết thúc khác nhau sau n+1 lượt chơi của Hân). Vậy Hân chỉ thắng khi cnt[<Hân>] == cntOutcomes và thua khi cnt[<Quý>] == cntOutcomes

CÁC PHẢN HỒI

Back to Top