ในข้อนี้โจทย์ต้องการหาช่วงที่ติดกันที่มีผลรวมมากที่สุดที่ยาวไม่เกิน
นิยามให้ หมายถึงความคุ้มค่าของบ้านหลังที่ และนิยามให้ หมายถึง prefix sum หรือก็คือ โดยเราสามารถคำนวณ ได้สำหรับทุก ๆ ตั้งแต่ ดังนั้นผลรวมความคุ้มค่าของช่วง จึงเท่ากับ
ดังนั้นการหาช่วงที่ติดกันที่มีผลรวมมากที่สุดสามารถเริ่มได้จากการกำหนดตัวแปร แทนผลรวมของช่วงที่มากที่สุดและ แทนความยาวช่วงที่น้อยที่สุดที่ช่วงนั้นมีผลรวมเท่ากับ โดยในตอนเริ่มต้นให้ และ ต่อมาไล่ในทุก ๆ ตั้งแต่ หากพิจารณา ณ ตำแหน่ง ใด ๆ โดย เป็นตำแหน่งสิ้นสุดของช่วง เราต้องการหาตำแหน่ง ที่ทำให้ กล่าวคือหาจุดเริ่มต้นช่วง ที่ทำให้ได้ความคุ้มค่าสูงสุดโดยความยาวช่วงไม่เกิน จากนั้นสามารถแยกพิจารณาออกเป็น 4 กรณีดังนี้
- กรณี 1: ไม่มีช่วงใดคุ้มค่าแก่การคุ้มครอง พิจารณา ถัดไป
- กรณี 2: แปลว่า ช่วง เป็นคำตอบที่ดีกว่า เปลี่ยนให้ และ
- กรณี 3: ค่าของ ยังคงเดิมแต่เมื่อผลรวมเท่ากันความยาวสั้นที่สุดจะเป็นคำตอบ ดังนั้นเปลี่ยนให้
- กรณี 4: ไม่มีโอกาสที่จะเป็นคำตอบแน่นอน ให้พิจารณา ถัดไป
เมื่อพิจารณาสำหรับทุก ๆ ตั้งแต่ แล้ว ยังคงเป็น แปลว่าไม่มีช่วงใดคุ้มค่าแก่การคุ้มครองเลย สามารถตอบด้วยคำตอบ และ ได้เลย
สำหรับแต่ละ การไล่ทุก ๆ ตั้งแต่ ถึง นั้นมี Time Complexity คือ โดยเมื่อคิดรวมทุก ๆ ตั้งแต่ จะมี Time Complexity รวม ซึ่งสามารถผ่านได้ใน Subtask 1 และ 2 เท่านั้น การจะ Optimize โปรแกรมให้เร็วขึ้นสามารถทำได้โดยใช้เทคนิค Minimum Sliding Window ด้วย Deque หรือ Double-ended Queue
การทำ Minimum Sliding Window ด้วย Deque นั้นจะเก็บเพียงข้อมูลที่ยังคงมีประโยชน์หรือสามารถนำไปใช้ได้ในอนาคตเท่านั้น กำหนด คือ Deque เก็บตัวแปรชนิด info
ซึ่งเป็น struct ที่ประกอบด้วย idx
คือ index ของ prefix sum ช่องต่าง ๆ ที่ยังเก็บไว้พิจารณา และ va
คือค่า สำหรับทุก ๆ ตั้งแต่
struct info { long long va, idx; }; deque<info> q;
โดยจะมีลำดับขั้นตอนดังนี้
-
ลบข้อมูลที่ไม่มีประโยชน์: ในขณะที่ และ
q.back().va
แปลว่าq.back().va
ไม่มีประโยชน์อีกต่อไปเพราะ index จะถูกเลือกแทนq.back().idx
แน่นอนเพราะนอกจากq.back().va
แล้วq.back().idx
อีกด้วย ดังนั้นสามารถลบข้อมูลนี้ทิ้งได้เลยด้วยการใช้q.pop_back()
-
ลบข้อมูลที่เกินขอบเขต: ในขณะที่ และ
q.front().idx
แปลว่าช่วง มีความยาวเกิน จึงไม่สามารถนำมาพิจารณาได้อีกต่อไป ดังนั้นสามารถลบข้อมูลนี้ทิ้งได้เลยด้วยการใช้q.pop_front()
-
เพิ่มข้อมูล: ข้อมูลในช่องที่ มีโอกาสที่จะเป็นคำตอบสำหรับ index ต่อ ๆ ไป ดังนั้นจึงต้องเก็บข้อมูลนี้ไว้พิจารณาต่อด้วย
q.push_back({pref(j),j})
การทำตามลำดับดังกล่าวจะทำให้ q.front().idx
และ q.front().va
เป็นค่าที่น้อยที่สุดใน แน่นอน จากเหตุผลดังกล่าวจะได้ว่า q.front().idx
คือคำตอบของ สามารถนำมาคิดได้ตาม 4 กรณีด้านบนได้เลย
for (int j = 1; j <= n; j++) { while (!q.empty() && pref[j] <= q.back().va) q.pop_back(); while (!q.empty() && q.front().idx < j - k) q.pop_front(); q.push_back({pref[j], j}); if (j == q.front().idx) { continue; } else if (mx < pref[j] - q.front().va) { mx = pref[j] - q.front().va; len = j - q.front().idx; } else if (mx == pref[j] - q.front().va) { len = min(len, j - q.front().idx); } }
Time Complexity รวมของ Minimum Sliding Window นั้นจะเหลือเพียง เพราะทุก ๆ ข้อมูลจะเข้ามาอยู่ใน ด้วย q.push_back()
เพียง 1 ครั้งและจะออกจาก ด้วย q.pop_back()
หรือ q.pop_front()
เพียง 1 ครั้งเช่นกัน เมื่อมีข้อมูลทั้งหมด ข้อมูลจึงมี Time Complexity รวม