Observation
- เราสามารถสังเกตได้ว่าในทุกๆคำถาม index ของ Array ที่ใช้คิด Sum จะมีผลหารด้วยค่า เท่ากันทั้งหมด เนื่องจาก
- เราจะสังเกตได้ว่าหากค่า น้อย จำนวนเศษที่แตกต่างกันที่ได้จากการด้วย ก็จะน้อยตามไปด้วย
- ถ้าหากเราเจอหลายคำถามที่ค่า เท่ากัน และ เท่ากันด้วย เราจะสามารถ Precompute Quicksum เพื่อมาตอบได้ใน เช่น และ คำถามที่ 1 จะใช้ index ดังต่อไปนี้ และคำถามที่ 2 จะใช้ index ซึ่งจะมีบางส่วนที่ซ้อนทับกันทำให้เราสามารถคิด Quicksum ล่วงหน้าเพื่อมาตอบทั้งสองคำถามนี้ได้
Approach 1
สำหรับข้อนี้ หากเราเราตอบแต่ละคำถามโดยการไล่ loop ตรงๆดังโค้ดด้านล่าง
int sum = 0; for (int i = l; i <= r; i += m) sum += arr[i]; cout << sum << " ";
จะใช้เวลา ซึ่งใช้เวลาแปรผกผันกับ กล่าวคือหาก มีค่าน้อยจะใช้เวลามาก และหาก มีค่ามากก็จะใช้เวลาน้อย ซึ่งถ้าเราเจอคำถามที่มีค่า น้อยหลายๆครั้งจะทำให้ใช้เวลา ซึ่งใช้เวลานานเกินไป
Approach 2
แต่ถ้าหากเราคิด Quicksum ไว้ล่วงหน้าเพื่อตอบแต่ละคำถามใน โดยเราจะสังเกตได้ว่าเราสามารถคิด Quicksum ดังสมการด้านล่างได้
แต่การทำแบบนี้จะทำให้ต้องคิดค่า Quicksum ล่วงหน้าสำหรับ ทุกๆ ทำให้มีทั้งหมด อัน และแต่ละอันใช้เวลา ทำให้ใช้เวลา Precompute Quicksum ล่วงหน้าทั้งหมดเท่ากับ ซึ่งใช้เวลานานเกินไป
แก้ปัญหา
เราจะใช้เทคนิค Square Root Decomposition มาช่วยในการแก้ปัญหาข้อนี้ (สามารถศึกษาจากคลิปนี้ได้ https://youtu.be/BJhzd_VG61k) กล่าวคือเราจะแบ่งวิธีการตอบแต่ละคำถามเป็น 2 วิธี
วิธีแรกสำหรับคำถามที่ใช้เวลา loop มาก(ค่า น้อย) และวิธีที่สองสำหรับคำถามที่ใช้เวลา loop น้อย(ค่า มาก) ซึ่งเราจะบอกว่าค่า น้อยก็ต่อเมื่อ มิฉะนั้นจะเรียกค่า ว่ามีค่ามาก
สำหรับ มากเราจะสามารถ for loop ตรงๆได้ เนื่องจากการ loop จะใช้เวลา แต่เนื่องจาก ทำให้ หรือก็คือใช้เวลา ต่อหนึ่งคำถาม และใช้เวลาไม่เกิน เมื่อ คือจำนวนคำถาม ตัวอย่างดังโค้ดด้านล่าง
if (m > SQ) { int sum = 0; for (int i = l; i <= r; i += m) sum += arr[i]; cout << sum << " "; }
สำหรับค่า น้อยเราจะคิด Quicksum ไว้ล่วงหน้าเพื่อตอบแต่ละคำถามใน ซึ่งเราจะต้อง Quicksum สำหรับทุกๆค่า ที่ โดยในแต่ละรอบของการคิด Quicksum จะใช้เวลา และต้องทำทั้งหมด รอบ ซึ่งจะใช้เวลา ในการคิด Quicksum สำหรับทุกค่าของ M ที่ น้อย และใช้เวลาไม่เกิน ตัวอย่างดังโค้ดด้านล่าง
int qs[MXSQ][MXN] = {}, SQ; void PreProcess(int N, vector<int> arr) { SQ = 1+sqrt(N); for (int sz = 1; sz <= SQ; sz++) { for (int i = 1; i <= N; i++) { qs[sz][i] = qs[sz][max(0, i-sz)] + arr[i]; } } } int get_sum(int l, int m, int r) { int k = (r-l)/m; return qs[m][l+m*k] - qs[m][max(0, l-m)]; }
สุดท้ายนี้เราก็นำทั้ง 2 กรณีมารวมกัน(แยกเคสกันด้วย if) ซึ่งจะใช้เวลาเท่ากับ
Code
// Sqrt decomposition (Precalculate sum for m <= sqrt(n), otherwise loops to get sum) // Author: JO // Date: 6/5/2022 #include <bits/stdc++.h> using namespace std; const int MXN = 1e5+10; const int MXSQ = 320; int qs[MXSQ][MXN] = {}, SQ; void PreProcess(int N, vector<int> arr) { SQ = 1+sqrt(N); for (int sz = 1; sz <= SQ; sz++) { for (int i = 1; i <= N; i++) { qs[sz][i] = qs[sz][max(0, i-sz)] + arr[i]; } } } int get_sum(int l, int m, int r) { int k = (r-l)/m; return qs[m][l+m*k] - qs[m][max(0, l-m)]; } int main(void) { ios_base::sync_with_stdio(0); cin.tie(0); int N, Q; cin >> N >> Q; vector<int> arr(N+5); for (int i = 1; i <= N; i++) cin >> arr[i]; PreProcess(N, arr); while (Q--) { int l, m, r; cin >> l >> m >> r; if (m <= SQ) { cout << get_sum(l, m, r) << " "; } else { int sum = 0; for (int i = l; i <= r; i += m) sum += arr[i]; cout << sum << " "; } } return 0; }