Hướng dẫn thuật toán

Trùm CUỐI 2025-12-02 1:06:22 2025-12-02 1:17:37

Link đề bài

Nhận xét:

Đây là một bài toán tối ưu về thao tác bit (Bitwise operation), cụ thể là phép XOR ( a \oplus b ). Do ràng buộc của l r lên đến 10^{18} , ta không thể duyệt tất cả các cặp (a, b) , mà cần một thuật toán hiệu quả hơn, dựa trên biểu diễn nhị phân của số.


1. Phân tích đề bài

  • Yêu cầu: Tìm giá trị lớn nhất của a \oplus b (XOR) với điều kiện l \le a \le b \le r .
  • Đầu vào: Hai số nguyên l r ( 1 \le l \le r \le 10^{18} ).
  • Đầu ra: Giá trị lớn nhất của a \oplus b .
  • Ràng buộc quan trọng: Cần sử dụng kiểu dữ liệu 64-bit (ví dụ: long long trong C++, int trong Python 3) để chứa l r .

2. Ý tưởng Thuật toán

Để tối đa hóa giá trị của a \oplus b , ta muốn các bit có trọng số lớn nhất (tính từ trái sang) của kết quả là 1. Bit thứ k của a \oplus b bằng 1 nếu và chỉ nếu bit thứ k của a b khác nhau (một là 0, một là 1).

Ý tưởng cốt lõi là tìm bit khác nhau quan trọng nhất giữa l r .

  1. Tính X = l \oplus r . X cho biết các vị trí bit mà l r khác nhau.

  2. Tìm vị trí bit có trọng số lớn nhất (Most Significant Bit - MSB) của X . Gọi vị trí đó là k .

    • Các bit ở vị trí i > k của l r là giống nhau. Do l \le a \le b \le r , nên các bit này của a b cũng buộc phải giống nhau. Do đó, a_i \oplus b_i = 0 .
    • Tại vị trí k , l_k \ne r_k . Vì l \le r , nên l_k phải là 0 và r_k phải là 1.
  3. Tối đa hóa XOR:

    • Để tối đa hóa a \oplus b , ta cần a b khác nhau ở vị trí k và tất cả các vị trí thấp hơn ( i < k ).
    • l r đã khác nhau ở bit k ( l_k=0, r_k=1 ), ta có thể chọn a có bit k=0 b có bit k=1 (hoặc ngược lại).
    • Sự khác biệt giữa l r ở bit k cho phép ta chọn các số a b trong phạm vi [l, r] sao cho a b khác nhau tại bit k và tất cả các bit thấp hơn.
  4. Kết quả tối đa: Giá trị XOR lớn nhất có thể đạt được là một số có các bit từ k trở xuống đều là 1.

    \text{Max XOR} = 2^{k+1} - 1

3. Các bước cơ bản của Thuật toán

  1. Đọc và xử lý đầu vào: Đọc hai số l r dưới dạng số nguyên 64-bit.
  2. Tính XOR: Tính X = l \oplus r .
  3. Trường hợp đặc biệt: Nếu X = 0 (tức là l=r ), kết quả là 0 \oplus 0 = 0 .
  4. Tìm vị trí MSB ( k ): Tìm vị trí bit k của bit 1 có trọng số lớn nhất trong X . ( k là chỉ số bắt đầu từ 0).
    • k = \lfloor \log_2 X \rfloor .
    • Ví dụ: X=2 = 10_2 . \log_2 2 = 1 . k=1 .
    • Ví dụ: X=25 = 11001_2 . \lfloor \log_2 25 \rfloor = 4 . k=4 .
  5. Tính kết quả: Kết quả lớn nhất là 2^{k+1} - 1 .

Minh họa cho Ví dụ: l=8, r=10

  1. l=8 \ (1000_2), r=10 \ (1010_2) .
  2. X = 8 \oplus 10 = 2 \ (0010_2) .
  3. MSB của X=2 là ở vị trí k=1 (bit thứ hai từ phải sang).
  4. Kết quả: 2^{1+1} - 1 = 2^2 - 1 = 3 . (Kiểm tra: Chọn a=9 \ (1001_2), b=10 \ (1010_2) . 9 \oplus 10 = 3 \ (0011_2) ).

4. Lưu ý khi triển khai bằng C++/Python

  • Kiểu dữ liệu: Bắt buộc phải dùng kiểu 64-bit:
    • C++: Sử dụng long long.
    • Python: Mặc định số nguyên là lớn, nên không cần lo lắng về kích thước.
  • Tìm MSB ( k ):
    • Sử dụng logarit: k = floor(log2(X)) (cần thư viện <cmath>).
    • Dùng hàm dựng sẵn (C++): Đối với long long, có thể dùng __builtin_clzll (Count Leading Zeros Long Long) để tính k nhanh chóng:
    // long long X;
    int num_leading_zeros = __builtin_clzll(X);
    int k = 63 - num_leading_zeros; // Giả sử 64-bit
    
    • Vòng lặp (cách đơn giản nhất): Dùng vòng lặp dịch bit để tìm vị trí k .
    k = 0
    while (1 << (k+1)) <= X:
        k += 1
    # hoặc đơn giản hơn:
    k = X.bit_length() - 1
    
  • Tính 2^{k+1} - 1 : Đây chính là 2^{k+1} trừ đi 1. Có thể tính bằng phép dịch bit:
    • Kết quả = (1LL \ll (k + 1)) - 1; (trong C++)
    • Kết quả = (1 << (k + 1)) - 1 (trong Python)