report VI VI EN
Register | Login
  • HOME
  • PROBLEMSET
  • ROADMAP
  • COMPETITION
  • TOPIC
  • RANKING
  • GUIDE
  • MASHUP
  • ABOUT
  • CONTACT
Solutions of Odd - FlandreOJ: Flandre Online Judge

Solutions of Odd

Select solution language

Write solution here.


chithanhnguyen    Created at    2 likes

Để làm được bài này bạn đọc cần biết về [XOR Hashing](https://codeforces.com/blog/entry/85900) và [Chia căn](https://usaco.guide/plat/sqrt?lang=cpp). ## Phân tích điều kiện xuất hiện lẻ lần Sử dụng kỹ thuật XOR Hashing để gán cho mỗi giá trị $x$ một hash ngẫu nhiên $h(x)$. Ta nhắc lại một tính chất quen thuộc của phép xor: $a \oplus a = 0$, hay tổng quát hơn: $$ \underbrace{a\oplus a \oplus \ldots \oplus a}_{\text{số chẵn lần}} = 0 $$ Như vậy khi ta dùng XOR Hashing để băm một multiset mà $x$ xuất hiện chẵn lần thì $h(x)$ sẽ không có đóng góp gì vào giá trị hash sau cùng. Qua đó ta có nhận xét quan trọng nhất cho bài toán: Một đoạn $a[l\ldots r]$ thỏa mãn khi và chỉ khi hash của đoạn $[l, r]$ bằng hash của các **giá trị phân biệt** trong đoạn. ## Lời giải Để tính xor các giá trị phân biệt trong một đoạn $[l, r]$ ta cố định $l$ là duyệt từ $n\rightarrow 1$. Mỗi khi "quét đến" $a_l$, ta sẽ gán giá trị gần nhất bên phải bằng $a_l$ (nếu có) thành $0$, khi đó xor của đoạn $[l, r]$ chính là xor các giá trị phân biệt. Như vậy ta có ý tưởng thực hiện như sau: - Cố định đầu $l$ và duyệt từ phải qua trái - Duy trì $pref_r = (h(a_l) \oplus h(a_{l + 1}) \oplus \ldots \oplus h(a_{r})) \oplus (\text{XOR các giá trị phân biệt trong đoạn }[l, r])$ - Khi gặp $a_l$, gọi $\texttt{last}[a_l]$ là vị trí gần nhất bên phải bằng $a_l$ (nếu có). - Thực hiện xor đoạn $[\texttt{last}[a_l], n]$ cho $h(a_l)$ (việc này sẽ cập nhật lại $pref_r$, để dễ hình dung thì bạn đọc nên vẽ ra và chú ý tính khử nhau của phép xor). - Như vậy số đoạn thỏa mãn có mút trái $l$ chính là số giá trị $0$ trong đoạn $pref[l\ldots r]$. Ta cần một cấu trúc dữ liệu hiệu quả để duy trì $pref$, trong lời giải này tác giả sử dụng chia căn, cụ thể là: - Chia mảng $pref$ thành các block độ dài $\sqrt{n}$, gán cho mỗi block một nhãn $\texttt{lazy}$ là giá trị xor được cập nhật lên block. - Với mỗi truy vấn xor đoạn $[l, r]$, ta cập nhật $\texttt{lazy}$ của các block nguyên vẹn và thủ công với các block ngoài. - Với mỗi truy vấn đếm số lượng, ta làm tương tự như trên và đếm số giá trị trong block bằng $\texttt{lazy}$. Như vậy độ phức tạp mỗi truy vấn là $O(\sqrt{n})$ từ đó thu được thuật toán độ phức tạp $O(n\sqrt{n})$. **Lưu ý.** Để lấy số lần xuất hiện của hash, bạn đọc nên tự cài hash map thay vì sử dụng `unordered_map` của C++.