trang chủ / bài tập / cardgame15 / lời giải

Trò Chơi Bài (Platinum)

Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Tác giả: huunguyenhuunguyen

Lời giải: Trò Chơi Bài Hai Luật

Hướng tiếp cận

Bài toán yêu cầu tìm điểm tối đa khi người A có thể đổi từ "bài cao thắng" sang "bài thấp thắng" đúng một lần ở bất kỳ vòng nào.

Gọi điểm đổi luật là sau vòng i (với 0iN, i=0 nghĩa là đổi luật ngay từ đầu). Khi đó:

  • N vòng đầu (vòng 1 đến i): luật "bài cao thắng".
  • Ni vòng sau (vòng i+1 đến N): luật "bài thấp thắng".

Đáp án = max0iN(prefix[i]+suffix[i])

trong đó:

  • prefix[i] = số điểm tối đa người A giành được trong i vòng đầu với luật "bài cao thắng", dùng i lá bài cao nhất của mình.
  • suffix[i] = số điểm tối đa người A giành được trong Ni vòng cuối với luật "bài thấp thắng", dùng Ni lá bài thấp nhất của mình.

Nhận xét quan trọng

  1. Phân chia bài tối ưu: Trong đoạn "bài cao thắng", người A nên dùng i lá bài lớn nhất; trong đoạn "bài thấp thắng", nên dùng Ni lá bài nhỏ nhất. Đây là vì không có lý gì dùng một lá bài nhỏ để cố đánh bại ở đoạn cao-thắng khi đã có bài lớn hơn.

  2. Tính prefix: Với i vòng đầu (người B đánh a1,a2,,ai) và i lá bài lớn nhất của người A, dùng chiến lược tham lam: dùng lá bài nhỏ nhất lớn hơn lá của B (nếu có).

  3. Tính suffix: Với Ni vòng cuối (người B đánh ai+1,,aN theo thứ tự ngược) và Ni lá bài nhỏ nhất của người A (theo thứ tự ngược), dùng chiến lược đối xứng.

Thuật toán

Để tính prefix[]suffix[] hiệu quả, ta dùng Segment Tree trên không gian lá bài [1..2N].

Cấu trúc nút Segment Tree:

  • covers: số lá bài của người A trong đoạn này chưa được sử dụng (lá bài có thể "che" bài của B).
  • coverables: số lá bài của người B trong đoạn này chưa được "che".
  • points: số điểm đã ghi nhận trong đoạn này.

Kết hợp hai nút (trái x, phải y):

ep = min(x.coverables, y.covers)   // bài B phía trái được che bởi bài A phía phải
points = x.points + y.points + ep
covers = x.covers + y.covers - ep
coverables = x.coverables + y.coverables - ep

Tính prefix[i]:

  • Lấy i lá bài lớn nhất của người A (đây là những lá bài không thuộc dãy B, theo thứ tự giảm dần).
  • Với j=1,2,,N: chèn lá bài B[j] vào vị trí tương ứng trong segment tree (gắn coverables = 1), đồng thời chèn lá bài lớn thứ j của người A (gắn covers = 1). Sau mỗi bước, prefix[j]=root.points.

Tính suffix[i]: Làm tương tự nhưng theo chiều ngược (vòng cuối trước, luật thấp thắng). Đổi biến: thay mỗi lá bài v bằng 2N+1v để quy về bài "thấp thắng" thành "cao thắng". Dùng lá bài nhỏ nhất của người A, xử lý từ vòng N ngược về 1.

Kết quả: maxi=0N(prefix[i]+suffix[Ni])

Độ phức tạp

  • Thời gian: O(NlogN) — mỗi thao tác chèn vào segment tree mất O(logN), thực hiện O(N) lần cho cả prefix và suffix.
  • Bộ nhớ: O(N).

Bình luận

Không có bình luận tại thời điểm này.

gnatmake 12.2.0 a68g 3.1.2 nasm 2.16.1 as_x64 2.46 awk 1.3.4 gcc 16.1.0 csc 6.12.0.200 g++ 16.1.0 g++-themis 16.1.0 g++17 16.1.0 g++20 16.1.0 g++23 16.1.0 clang++ 22.1.6 dmd 2.112.0 dart 3.12.1 gforth 0.7.3 gfortran 12.2.0 go 1.26.3 groovyc 5.0.6 javac 25.0.3 node 26.2.0 kotlinc 2.3.21 sbcl 2.2.9 lua 5.4.8 nim 2.2.10 fpc 3.2.2 fpc-themis 3.2.2 perl 5.36.0 php 8.5.6 pike 8.0 pypy3 7.3.23 python3 3.14.5 racket 8.7 ruby 4.0.5 rustc 1.96.0 csc 5.3.0 ctoj-scratch 0.0.1 sed 4.9 tclsh 8.6 bun 1.3.14 deno 2.8.1 v 0.5.1 zig 0.16.0