โจทย์ข้อนี้เป็นแนว Dynamic Programming
เราจะนิยามให้ คือจำนวนปืนใหญ่ตั้งแต่ช่องที่ ถึงช่องที่
สังเกตว่าจำนวนปืนใหญ่ตั้งแต่ช่องที่ ถึง เท่ากับจำนวนปืนใหญ่ตั้งแต่ช่องที่ ถึง บวกด้วยปืนใหญ่ในช่องที่ หรือสามารถเขียนได้ว่า ซึ่ง จะเท่ากับ 0 ถ้าช่องที่ ไม่มีปืนใหญ่ และเท่ากับ 1 ถ้าช่องที่ มีปืนใหญ่
ถ้าตำแหน่งจัดวางจุดลำเลียงกระสุนอยู่ที่ และความยาวครึ่งหนึ่งของรางลำเลียงเท่ากับ แปลว่ารางลำเลียงกระสุนจะอยู่ตั้งแต่ ถึง
เราสามารถคำนวนจำนวนปืนใหญ่ตั้งแต่ ถึง ได้จาก จำนวนปืนใหญ่ตั้งแต่ ถึง ลบด้วยจำนวนปืนใหญ่ตั้งแต่ ถึง หรือก็คือ
ปัญหาของข้อนี้
เนื่องจากจุดลำเลียงกระสุนอาจมีมากกว่า จุดได้ แสดงว่าอาจมีรางที่ทับซ้อนกันได้
สมมติให้รางที่ อยู่ที่ และรางที่ อยู่ที่ โดยโจทย์รับประกันว่าถ้า แล้ว
เราจะสามารถแยกได้ 2 กรณี
กรณีที่ 1 รางที่ และ มีช่วงที่ทับกัน
กรณีนี้จะเกิดถ้า กรณีนี้จะเห็นว่าเราจะนับปืนใหญ่ในช่วงที่ซ้ำกัน 2 ครั้ง ซึ่งเราจะต้องหาทางในการคำนวนจำนวนปืนใหญ่ที่จะอธิบายหลังจากพูดถึงกรณีที่ 2
กรณีที่ 2 รางที่ และ ไม่มีช่วงที่ทับกัน
กรณีนี้จะเกิดถ้า ตรงนี้สามารถคำนวนจำนวนปืนใหญ่โดยวิธีข้างต้นได้เลย
การแก้ปัญหากรณีที่ 1
หลักการแก้ปัญหาคือ ถ้ารางมีที่ส่วนทับกัน เราจะรวมให้เป็นรางเดียว เช่น รางที่ 1 , รางที่ 2 , รางที่ 3 เวลาเราคำนวนจำนวนปืนใหญ่จะคำนวนเป็นรางใหญ่ เลย
ดังนั้นเราจะเริ่มพิจารณาตั้งแต่รางที่ ถึงช่วงที่ โดยที่เราจะสมมติให้รางที่ เป็นรางหลัก แล้วเมื่อเราพิจารณารางถัดไป ก็พิจารณาว่ามีส่วนทับกับรางหลักหรือไม่ ถ้ามีให้รวมเข้ากับรางหลักแล้วเลื่อนไปพิจารณารางถัดไป ถ้าไม่ทับกันให้คำนวนจำนวนปืนใหญ่ของรางหลักโดยวิธีที่ได้กล่าวไปข้างต้น แล้วให้รางนี้เป็นรางหลัก
ยกตัวอย่าง
ตำแหน่งจุดลำเลียงกระสุนอยู่ที่ และความยาวครึ่งรางเท่ากับ
เราจะได้ช่วงของรางลำเลียงคือ
- เริ่มจากให้รางที่ เป็นรางหลัก
- พิจารณารางที่ จะเห็นว่าไม่มีช่วงที่ทับกับรางหลัก ดังนั้นเราจะคำนวนจำนวนปืนใหญ่ของรางหลัก แล้วให้ เป็นรางหลัก
- พิจารณารางที่ จะเห็นว่ามีช่วงที่ทับกับรางหลัก เราก็จะเอารางที่ รวมกับรางหลักกลายเป็น
- พิจารณารางที่ จะเห็นว่าไม่มีช่วงที่ทับกับรางหลัก ดังนั้นเราจะคำนวนจำนวนปืนใหญ่ของรางหลัก แล้วให้ เป็นช่วงหลัก
- คำนวนจำนวนปืนใหญ่ของรางหลักสุดท้าย
โค้ดตัวอย่างในภาษา C++
#include <bits/stdc++.h> using namespace std; const int MXN = 1e7 + 1; int cannon[MXN]; int main() { int n, m, k, l; scanf("%d %d %d %d", &n, &m, &k, &l); for (int i = 0; i < n; i++) { int x; scanf("%d", &x); x++; //บวกเพื่อป้องกันกรณีคิดตั้งแต่ช่องที่ 0 cannon[x]++; } for (int i = 1; i < MXN; i++) { cannon[i] += cannon[i - 1]; } for (int i = 0; i < k; i++) { int now_low = -1, now_up = -1, ans = 0; for (int j = 0; j < m; j++) { int now, lower, upper; scanf("%d", &now); now++; //บวกเพื่อป้องกันกรณีที่คิดตั้งแต่ช่องที่ 0 lower = max(1, now - l); upper = min(MXN - 1, now + l); if (now_low == -1) { now_low = lower; now_up = upper; } else if (now_up >= lower) { now_up = upper; } else { ans += cannon[now_up] - cannon[now_low - 1]; now_low = lower; now_up = upper; } } ans += cannon[now_up] - cannon[now_low - 1]; printf("%d\n", ans); } }
ข้อสังเกต: วิธีนี้เราต้องประกาศ Array INT32 จำนวน ช่องจะใช้ Memory ประมาณ Bytes = MB
Challenge: ลองคิดวิธีที่สามารถลดการใช้ Memory ลงได้
Time Complexity