Editorial for Bài học về sàng số nguyên tố đầu tiên


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.
Submitting an official solution before solving the problem yourself is a bannable offence.

Điều kiện của một dãy ~a~ hợp lệ là với mỗi hợp số ~a_i~, cần phải tồn tại một số nguyên tố ~a_j~ là ước của ~a_i~ mà ~j < i~. Từ đây ta có ý tưởng phải di chuyển các số nguyên tố lên trước hợp số là bộ của nó.

Ngoài ra ta không thể đảo chỗ 2 thừa số nguyên tố. Do để duy chuyển một ước nguyên tố ~a_j~ lên trước ~a_i~ (khi mà ~j > i~), ta cũng cần phải di chuyển phần tử nguyên tố nằm trong ~a_{i + 1}, a_{i + 1}, \ldots, a_{j - 1}~ lên trước ~a_i~.

Như vậy với subtask 1, ta có thể thực hiện thuật toán sau:

for i := 1 to n do:
  if a[i] is not composite: continue  // duyệt i mà a[i] là hợp số
  for j in 1 to n do:
    if a[j] is not prime: continue    // duyệt j mà a[j] là số nguyên tố
    move a[j] before a[i]
    if a[i] \% a[j] == 0: break        // dừng khi đã có ước nguyên tố phía trước
  end for
end for

Với subtask 2, ta có một vài nhận xét sau:

  • Số bước cần duyệt chuyển ~a_j~ lên trước ~a_i~ chỉ phụ thuộc vào số hợp số nằm giữa ~a_j~ và ~a_i~, bởi vì các số nguyên tố nằm giữa ta cũng phải di chuyển lên trước ~a_i~.

    • Để tính số bước di chuyển nhanh, ta có thể tính số hợp số nằm trước vị trí ~i~ với mọi ~i~ và dùng hiệp để tính số bước di chuyển.
  • Khi đã thực hiện di chuyển xong ~a_j~, ta có thể đánh dấu toàn bộ bội của ~a_j~ là đã xét, để sau đó ta không cần thực hiện xét nữa.

Quan sát này giúp tối ưu 2 vòng lặp lồng nhau ở phía từ ~O(n^2)~ thành ~O(n\log n)~ khi sử dụng 2 con trỏ và mảng đánh dấu.

use std::io::*;

fn solve(a: Vec<usize>) -> usize {
    let n = a.len() + 1;
    let mut is_prime = vec![true; n + 1];
    for i in 2..=n {
        if is_prime[i] {
            for j in (i * i..=n).step_by(i) {
                is_prime[j] = false;
            }
        }
    }
    let mut target_prime_div = vec![0; n + 1];
    for &i in a.iter() {
        if !is_prime[i] {
            continue;
        }
        for j in (i..=n).step_by(i) {
            if target_prime_div[j] == 0 {
                target_prime_div[j] = i;
            }
        }
    }
    let mut prime_marked = vec![false; n + 1];
    let mut cnt_composite_before = vec![0; n + 1];
    for i in 0..a.len() {
        cnt_composite_before[i + 1] = cnt_composite_before[i] + (!is_prime[a[i]] as usize);
    }

    let mut ans = 0;
    let mut f = 0;
    for i in 0..a.len() {
        if !is_prime[a[i]] {
            continue;
        }
        f = (f..i).skip_while(|&x| prime_marked[target_prime_div[a[x]]]).next().unwrap_or(i);
        ans += cnt_composite_before[i] - cnt_composite_before[f];
        prime_marked[a[i]] = true;
    }

    ans
}

fn main() {
    let mut stdin = stdin().lock();
    let mut writer = BufWriter::new(stdout().lock());
    let mut read_line = || -> String {
        let mut s = String::new();
        stdin.read_line(&mut s).unwrap();
        s
    };
    let parse_usize = |s: &str| s.parse::<usize>().unwrap();

    let n_test = parse_usize(&read_line().trim());
    for _test_case in 1..=n_test {
        let _n = parse_usize(&read_line().trim());
        let a = read_line().trim().split_whitespace().map(parse_usize).collect::<Vec<_>>();

        let ans = solve(a);
        writeln!(writer, "{}", ans).unwrap();
    }
}

Comments

Please read the guidelines before commenting.


There are no comments at the moment.