Hướng dẫn giải của Chọn Đội tuyển HSGQG TPHCM 2025 - Phù thủy


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.

Nếu ta xem giá trị của mỗi loại phép thuật ~x~ là ~2^x~, và giá trị của mỗi phủ thuỷ là xor của tất cả các loại phép thuật, thì bài toán quy về: chia đoạn ~[l,r]~ thành nhiều đoạn nhất, sao cho xor của mỗi đoạn là ~2^m-1~. Gọi ~a_i~ là giá trị của phù thuỷ thứ ~i~.

Subtask 1: Mỗi vị trí ~i~ có 2 lựa chọn: hoặc ghép cùng nhóm với vị trí ~i-1~, hoặc bắt đầu một nhóm mới. Như vậy ta có thể duyệt toàn bộ ~2^n~ cách chia nhóm trong ~\mathcal{O}(2^n \cdot n)~ bằng duyệt bitmask hoặc ~\mathcal{O}(2^n)~ bằng đệ quy.

Subtask 2: Mỗi truy vấn cập nhật trong ~\mathcal{O}(1)~, trả lời trong ~\mathcal{O}(n)~.

  • Quy hoạch động:

    • Gọi ~dp_i~ là số đoạn nhiều nhất có thể khi chia đoạn ~[l,i]~.

    • Công thức: ~dp_{l-1}=0~, ~dp_i \leftarrow dp_j+1~ nếu ~a_{j+1} \oplus \ldots \oplus a_i = 2^m-1~, hay ~pref_i \oplus pref_j = 2^m-1~ với ~pref_i~ là xor tiền tố từ ~l~ đến ~i~.

    • Để tính trong ~\mathcal{O}(n)~, ta lưu thêm một mảng ~best_x~ là ~dp_j~ tốt nhất với ~j<i~ và ~pref_j = x~. Khi đó với mỗi ~i~, ta cập nhật ~dp_i \leftarrow best_{(2^m-1) \oplus pref_i}~ và ~best_{pref_i} \leftarrow dp_i~.</p>

    • Tuy độ phức tạp là ~\mathcal{O}(q \cdot n)~ nhưng cách này constant khá lớn và có thể không AC.

  • Tham lam:

    • Duyệt ~i~ từ ~l~ đến ~r~, nếu xor hiện tại là ~2^m-1~ ~\Rightarrow~ cắt thành một đoạn luôn.

    • Trường hợp còn thừa đoạn cuối không chia được: nếu giá trị của nó là ~0~ thì ghép với đoạn có giá trị ~2^m-1~ trước đó, ngược lại kết luận đáp án là ~-1~.

    • Độ phức tạp ~\mathcal{O}(q \cdot n)~ constant bé.

Subtask 3: Không có truy vấn cập nhật, nên ta có thể mô phỏng thuật tham bằng Binary Lifting.

  • Gọi ~nxt_i~ là vị trí gần ~i~ nhất và ~a_i \oplus \ldots \oplus a_{nxt_i} = 2^m-1~.

  • Khi đó với mỗi truy vấn ta chỉ cần gán liên tục ~l \rightarrow nxt_l+1 \rightarrow nxt_{nxt_l+1}+1 \ldots~, cho đến khi ~nxt_l>r~. Xét giá trị đoạn cuối: nếu nó là ~0~ hoặc ~2^m-1~ thì đáp án là số lần nhảy (~+1~ nếu như là ~2^m-1~), ngược lại là ~-1~.

  • Tuy nhiên việc gán như vậy có thể mất ~\mathcal{O}(n)~ mỗi truy vấn, ta chuẩn bị trước mảng ~nxt_{ij}~ là kết quả khi nhảy ~2^j~ lần từ vị trí ~i~, và duyệt các bit ~j~ từ lớn về bé với mỗi truy vấn để lấy kết quả từ mảng ~nxt~. Độ phức tạp ~\mathcal{O}((n+q) \log n)~

Subtask 4: Mô phỏng thuật tham bằng Segment Tree. Cụ thể, với mỗi nút ~u~ biểu diễn đoạn ~[l,r]~ ta lưu:

  • ~value~: xor của đoạn ~[l,r]~.

  • ~cnt[u]~: số đoạn chia được nếu giá trị xor ban đầu là ~u~ (nghĩa là trước khi duyệt các phần tử trong đoạn ~[l,r]~ thì giá trị xor đã có sẵn ~u~).

  • ~rem[u]~: phần dư của đoạn cuối nếu giá trị xor ban đầu là ~u~

Nút lá ~[i,i]~ có dạng:

  • ~value=a_i~

  • Nếu ~msk \oplus a_i = 2^m-1~ thì ~cnt[msk]=1~, ~rem[msk]=0~.

  • Ngược lại ~cnt[msk] = 0~, ~rem[msk]=a[i]~.

Ghép 2 nút ~u,v~: Khi đã biết phần còn thừa của ~u~, ta có thể truy vấn vị trí tương ứng ở ~v~ để biết phần thừa khi ghép đoạn ~v~ sau đoạn ~u~, cụ thể:

  • ~value = u.value \oplus v.value~

  • ~cnt[msk] = u.cnt[msk] + v.cnt[u.rem[msk]]~

  • ~rem[msk] = v.rem[u.rem[msk]]~

Từ đó bài toán quy về cập nhật điểm và truy vấn đoạn. Nếu xor đoạn ~[l,r]~ khác ~0~ và ~2^m-1~, hoặc ~cnt[0] = 0~, thì đáp án là ~-1~, ngược lại đáp án là ~cnt[0]~. Độ phức tạp: ~\mathcal{O}((n + q) \cdot 32 \cdot \log(n))~

Chứng minh thuật tham đúng

Đặt ~S=2^m-1~.

Giả sử chia được thành ~k~ nhóm hợp lệ, khi đó xor cả đoạn sẽ là ~0~ nếu ~k~ chẵn hoặc ~S~ nếu ~k~ lẻ. In ~-1~ nếu xor cả đoạn không thuộc 1 trong 2 trường hợp trên.

Dễ thấy cách tham trên sẽ luôn cho ra số nhóm lớn nhất. Giả sử cấu hình tìm được của cách tham là ~\{i_1, i_2, \ldots, i_k\}~, và cấu hình tốt hơn là ~\{i'_1, i'_2, \ldots, i'_{k'}\}~ với ~k' > k~. Khi đó sẽ có một điểm ~j~ có ~i'_j > i_j~, nhưng rõ ràng lùi ~i'_j~ về ~i_j~ thì sẽ có nhiều cơ hội tìm được thêm đoạn hơn ~\Rightarrow~ ~k = k'~.

Mặt khác, do xor cả đoạn chỉ là ~0~ hoặc ~S~, nên nếu thừa ra một đoạn không phải xor bằng ~S~, xor của nó chắc chắn là ~0~. Khi đó, nếu đã có 1 đoạn bằng ~S~ trước đó, ta chỉ cần ghép đoạn ~0~ này vào cùng đoạn ~S~ trước đó là được.


Bình luận

Hãy đọc nội quy trước khi bình luận.


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