เขื่อนกันคลื่น

toi12_barrier

ในข้อนี้โจทย์ต้องการหาช่วงที่ติดกันที่มีผลรวมมากที่สุดที่ยาวไม่เกิน ww

นิยามให้ va[i]va[i] หมายถึงความคุ้มค่าของบ้านหลังที่ ii และนิยามให้ pref(i)pref(i) หมายถึง prefix sum หรือก็คือ j=1iva[j]\sum \limits_{j=1}^{i} va[j] โดยเราสามารถคำนวณ pref(i)=pref(i1)+va[i]pref(i) = pref(i-1) + va[i] ได้สำหรับทุก ๆ ii ตั้งแต่ i=1,2,3,...ni = 1,2,3,...n ดังนั้นผลรวมความคุ้มค่าของช่วง [i,j][i,j] จึงเท่ากับ pref(j)pref(i1)pref(j) - pref(i-1)

ดังนั้นการหาช่วงที่ติดกันที่มีผลรวมมากที่สุดสามารถเริ่มได้จากการกำหนดตัวแปร mxmx แทนผลรวมของช่วงที่มากที่สุดและ lenlen แทนความยาวช่วงที่น้อยที่สุดที่ช่วงนั้นมีผลรวมเท่ากับ mxmx โดยในตอนเริ่มต้นให้ mx=0mx = 0 และ len=len = \infty ต่อมาไล่ในทุก ๆ jj ตั้งแต่ j=1,2,3,...,nj = 1,2,3,...,n หากพิจารณา ณ ตำแหน่ง jj ใด ๆ โดย jj เป็นตำแหน่งสิ้นสุดของช่วง เราต้องการหาตำแหน่ง ii ที่ทำให้ pref(i)=minmax(1,jw+1)kj{pref(k)}pref(i) = \min \limits_{\max(1, j - w + 1) \leq k \leq j} \{ pref(k) \} กล่าวคือหาจุดเริ่มต้นช่วง ii ที่ทำให้ได้ความคุ้มค่าสูงสุดโดยความยาวช่วงไม่เกิน mm จากนั้นสามารถแยกพิจารณาออกเป็น 4 กรณีดังนี้

  • กรณี 1: i=ji=j ไม่มีช่วงใดคุ้มค่าแก่การคุ้มครอง พิจารณา jj ถัดไป
  • กรณี 2: pref(j)pref(i)>mxpref(j) - pref(i) > mx แปลว่า ช่วง [i+1,j][i+1,j] เป็นคำตอบที่ดีกว่า เปลี่ยนให้ mx=pref(j)pref(i)mx = pref(j) - pref(i) และ len=jilen = j - i
  • กรณี 3: pref(j)pref(i)=mxpref(j) - pref(i) = mx ค่าของ mxmx ยังคงเดิมแต่เมื่อผลรวมเท่ากันความยาวสั้นที่สุดจะเป็นคำตอบ ดังนั้นเปลี่ยนให้ len=min(len,ji)len = \min ( len, j - i )
  • กรณี 4: pref(j)pref(i)<mxpref(j) - pref(i) < mx ไม่มีโอกาสที่จะเป็นคำตอบแน่นอน ให้พิจารณา jj ถัดไป

เมื่อพิจารณาสำหรับทุก ๆ jj ตั้งแต่ j=1,2,3,..nj = 1,2,3,..n แล้ว mxmx ยังคงเป็น 00 แปลว่าไม่มีช่วงใดคุ้มค่าแก่การคุ้มครองเลย สามารถตอบด้วยคำตอบ mx=0mx = 0 และ len=0len = 0 ได้เลย

สำหรับแต่ละ jj การไล่ทุก ๆ ii ตั้งแต่ max(1,jw+1)\max ( 1, j-w+1 ) ถึง jj นั้นมี Time Complexity คือ O(N)\mathcal{O}(N) โดยเมื่อคิดรวมทุก ๆ jj ตั้งแต่ j=1,2,3,...nj = 1,2,3,...n จะมี Time Complexity รวม O(N2)\mathcal{O}(N^2) ซึ่งสามารถผ่านได้ใน Subtask 1 และ 2 เท่านั้น การจะ Optimize โปรแกรมให้เร็วขึ้นสามารถทำได้โดยใช้เทคนิค Minimum Sliding Window ด้วย Deque หรือ Double-ended Queue

การทำ Minimum Sliding Window ด้วย Deque นั้นจะเก็บเพียงข้อมูลที่ยังคงมีประโยชน์หรือสามารถนำไปใช้ได้ในอนาคตเท่านั้น กำหนด qq คือ Deque เก็บตัวแปรชนิด info ซึ่งเป็น struct ที่ประกอบด้วย idx คือ index ของ prefix sum ช่องต่าง ๆ ที่ยังเก็บไว้พิจารณา และ va คือค่า pref(idx)pref(idx) สำหรับทุก ๆ jj ตั้งแต่ j=1,2,3,...,nj=1,2,3,...,n

struct info {
  long long va, idx;
};
deque<info> q;

โดยจะมีลำดับขั้นตอนดังนี้

  1. ลบข้อมูลที่ไม่มีประโยชน์: ในขณะที่ q>0|q| > 0 และ pref(j)pref(j) \leq q.back().va แปลว่า q.back().va ไม่มีประโยชน์อีกต่อไปเพราะ index jj จะถูกเลือกแทน q.back().idx แน่นอนเพราะนอกจาก pref(j)pref(j) \leq q.back().va แล้ว jj \geq q.back().idx อีกด้วย ดังนั้นสามารถลบข้อมูลนี้ทิ้งได้เลยด้วยการใช้ q.pop_back()

  2. ลบข้อมูลที่เกินขอบเขต: ในขณะที่ q>0|q| > 0 และ q.front().idx <jw< j - w แปลว่าช่วง [q.front().idx+1,j][q.front().idx+1,j] มีความยาวเกิน ww จึงไม่สามารถนำมาพิจารณาได้อีกต่อไป ดังนั้นสามารถลบข้อมูลนี้ทิ้งได้เลยด้วยการใช้ q.pop_front()

  3. เพิ่มข้อมูล: ข้อมูลในช่องที่ jj มีโอกาสที่จะเป็นคำตอบสำหรับ index ต่อ ๆ ไป ดังนั้นจึงต้องเก็บข้อมูลนี้ไว้พิจารณาต่อด้วย q.push_back({pref(j),j})

การทำตามลำดับดังกล่าวจะทำให้ jj - q.front().idx +1w+ 1 \leq w และ q.front().va เป็นค่าที่น้อยที่สุดใน qq แน่นอน จากเหตุผลดังกล่าวจะได้ว่า q.front().idx คือคำตอบของ jj สามารถนำมาคิดได้ตาม 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 นั้นจะเหลือเพียง O(N)\mathcal{O}(N) เพราะทุก ๆ ข้อมูลจะเข้ามาอยู่ใน qq ด้วย q.push_back() เพียง 1 ครั้งและจะออกจาก qq ด้วย q.pop_back() หรือ q.pop_front() เพียง 1 ครั้งเช่นกัน เมื่อมีข้อมูลทั้งหมด nn ข้อมูลจึงมี Time Complexity รวม O(N)\mathcal{O}(N)