ตัวหาร (divisor)

1120

โจทย์ข้อนี้ต้องการให้เราตอบคำถามว่าในช่วง XX ถึง Y Y มีกี่ตัวที่มีตัวหาร DD ตัว โดยมี QQ คำถาม

(1XY1000000,D500,Q100)(1\leq X \leq Y \leq 1000000, D \leq 500, Q \leq 100)

Preprocessing

สำหรับข้อนี้ขั้นแรกคือ preprocess ว่าแต่ละตัวเลขตั้งแต่ 11 ถึง L=1000000L=1000000 มีตัวหารกี่ตัว เราสามารถใช้วิธีคล้ายตะแกรงของเอราทอสเทนีส (Sieve of Eratosthenes)

สำหรับทุกจำนวนเต็ม xx เราจะ initialize จำนวนตัวหาร num_divisors[x] เป็น 0

จากนั้นสำหรับ 1iL1 \leq i \leq L เราจะเพิ่ม num_divisors[j] สำหรับทุก j=i,2i,3i,(jL)j = i, 2i, 3i, \dots (j \leq L) เพราะ ii เป็นตัวหารของ jj

เห็นได้ว่าแต่ละ ii จะต้องเพิ่ม jj จำนวน Li\frac{L}{i} ตัว

  int L = 1000000;
  int num_divisors[1000100];

  for (int i = 1; i <= L; i++)
    for (int j = i; j <= L; j += i)
      num_divisors[j]++;

ขั้นตอนนี้จึงใช้เวลา L1+L2+L3++LL=O(LlogL)\frac{L}{1} + \frac{L}{2} + \frac{L}{3} + \dots + \frac{L}{L} = \mathcal{O}(L \log{} L)

จากนั้นเราจะเก็บทุกตัวใน C[D] โดย C[D] เป็น vector เก็บตัวเลขที่มีตัวหาร DD ตัว

  vector < int > C[510];
  for (int i = 1; i <= L; i++)
    if (num_divisors[i] <= 500)
      C[num_divisors[i]].push_back(i);

สังเกตว่าการ insert ii เข้าไปใน vector ในแบบนี้จะทำให้แต่ละ vector เรียงกันจากน้อยไปมาก

ขั้นตอนนี้ใช้เวลา O(L)\mathcal{O}(L)

การตอบ Query

สำหรับ Query X,Y,DX, Y, D เราต้อง Binary Search หาตำแหน่งของค่าน้อยสุดที่ไม่น้อยกว่า XX ใน C[D] และตำแหน่งของค่าที่น้อยที่สุดที่มากกว่า YY จากนั้นเมื่อนำมาลบกันจะเป็นคำตอบของคำถาม เราสามารถใช้ Binary Search เพราะข้อมูลใน C[D] เรียงจากน้อยไปมากอยู่แล้ว

การ Binary Search ในแต่ละคำถามใช้เวลา O(logL)\mathcal{O}(\log{} L) เพราะแต่ละ vector มีขนาดอย่างมาก LL

หากใช้ภาษา C++ สามารถใช้ std::lower_bound และ std::upper_bound จาก <algorithm> ใน STL สำหรับการ Binary Search ดังกล่าว

  for (int i = 1; i <= Q; i++) {
    int X, Y, D;
    cin >> X >> Y >> D;

    auto lb = lower_bound(C[D].begin(), C[D].end(), X);
    auto ub = upper_bound(C[D].begin(), C[D].end(), Y);

    cout << int(ub - lb) << "\n";
  }

(std::lower_bound(C[D].begin(), C[D].end(), X) จะ return iterator ไปยังตำแหน่งแรกที่ไม่น้อยกว่า XX ส่วน std::upper_bound(C[D].begin(), C[D].end(), Y) จะ return iterator ไปยังตำแหน่งแรกที่มากกว่า YY) 7:51 AM 9/10/2023

ทั้งปัญหานี้จึงใช้เวลา O(LlogL+L+QlogL)=O((Q+L)logL)\mathcal{O}(L \log{} L + L + Q \log{} L) = \mathcal{O}((Q+L) \log{} L)