Slope Trick

09-07-2023

Properties

สมบัติของฟังก์ชันที่ทำ Slope Trick ได้

  1. เป็นฟังก์ชันต่อเนื่องบนจำนวนจริง
  2. ฟังก์ชันนั้นสามารถแบ่งออกเป็นฟังก์ชันย่อย แล้วแต่ละฟังก์ชันย่อยเป็นฟังก์ชันเส้นตรงที่มีความชันเป็นจำนวนเต็ม
  3. ฟังก์ชันนั้นต้องเป็น Convex Function (ฟังก์ชันที่เกิดจุดสูงสุดได้) หรือ Concave (ฟังก์ชันที่เกิดจุดต่ำสุดได้)

ข้อสังเกต ฟังก์ชันที่ทำ Slope Trick ได้มักจะมี fk()=fk()f_{k}(\infty) = -f_{k}(-\infty)

Tutorial

ในการทำ Slope Trick จะใช้ Data Structure [D][D] ในการเก็บจุดเปลี่ยนความชันของกราฟ โดยอาจใช้ std::multiset หรือ std::vector ในการเก็บได้

ค่าที่เก็บใน [D][D] นั้นจะเป็นจุดเปลี่ยนความชันที่ทำให้ความชันเปลี่ยนไป 11 เช่น

ในการเก็บเส้น f1(x)=10xf_1(x)=|10-x| นั้นจะเก็บจุดตัดเป็น [D]={10,10}[D] = \{10, 10\}

ถ้าเพิ่ม f2(x)=8xf_2(x)=|8-x| เข้าไปอีกเส้นจะทำให้ D={8,8,10,10}D=\{8,8,10,10\}

ฟังก์ชันของสองเส้นนี้รวมกันก็จะเป็น s(x)=f1(x)f2(x)=10x8xs(x)=f_1(x)- f_2(x)=|10-x| - |8-x|

สังเกตว่า s(x)s(x) (เส้นสีม่วง) จะมีจุดเปลี่ยนความชันที่ 88 และ 1010 และแต่ละครั้งที่เปลี่ยนคือ +2+2 ทำให้ต้องเก็บจุดนั้น ๆ 22 ครั้ง

ในบางครั้ง [D][D] ก็อาจจะจำเป็นต้องเก็บ y-intercept (จุดตัดแกน y) และ slope (ความชัน) ร่วมด้วยได้

ในการรวมเส้น 22 เส้นเข้าด้วยกัน y-intercept จะกลายเป็น c1+c2c_1 + c_2 เมื่อ cic_i แทนจุดตัดแกน y ของเส้นที่ ii และรวมถึง slope ก็จะกลายเป็น m1+m2m_1 + m_2 เมื่อ mim_i แทนความชันของเส้นที่ ii ด้วย

ปัญหาที่ใช้ Slope Trick ในการแก้

Median (การหาค่ามัธยฐาน)

โจทย์ กำหนดให้ y=A1x+A2x+A3x++Anxy = |A_1 - x| + |A_2 - x| + |A_3 - x| + \dots + |A_n - x| จงหาค่า yy ที่น้อยที่สุดที่เป็นไปได้

กำหนดให้ fi(x):=A1x+A2x+A3x++Aixf_i(x) := |A_1 - x| + |A_2 - x| + |A_3 - x| + \dots + |A_i - x| ที่มีค่าน้อยที่สุดเมื่อ xZx \in \mathbb{Z}
ดังนั้น fi(x)=Aix+fi1(x)f_i(x) = |A_i - x| + f_{i-1}(x) หากกำหนดให้ li=Aixl_i = |A_i - x| แล้ว fi(x)=li+fi1(x)f_i(x) = l_i + f_{i - 1}(x)

และเมื่อนิยาม fn(x)f_n(x) แบบข้างต้นแล้วจะมีค่า xx ที่ดีที่สุดที่ทำให้ (x,f(x))(x, f(x)) เป็นจุดต่ำสุดของ y=fn(x)y = f_n(x)

จริง ๆ แล้วปัญหานี้สามารถพิสูจน์ได้ว่า ค่าที่ดีที่สุดของ xx คือค่ามัธยฐานของ AiA_i ที่ i=1,2,,ni = 1, 2, \dots, n โดยสามารถไปตามอ่านบทพิสูจน์ได้จาก Math Stack Exchange

ตัวอย่างการทำงาน

y=10x+8x+9xy=|10-x|+|8-x|+|9-x|

พิจารณา f_2(x)=|10-x|+|8-x|

สังเกตเส้น l1=10xl_1 = |10 - x| (เส้นสีแดง) และ l2=8xl_2 = |8 - x| (เส้นฟ้า) เมื่อรวมกันจะได้ f2(x)f_2(x) (เส้นสีม่วง) พอดี

จากนั้นเมื่อใส่ l3=9xl_3 = |9 - x| เข้าไปจะได้

l3l_3 (เส้นสีเหลือง) เมื่อรวมเข้ากับ f2(x)f_2(x) จะได้ f3(x)f_3(x) (เส้นสีดำ) และเมื่อพิจารณาจุดที่ต่ำที่สุดของ f3(x)f_3(x) จะได้ค่ามัธยฐานของ AiA_i ซึ่งมีค่าเท่ากับ 99 และเมื่อแทน x=9x = 9 ลงไปใน f3(x)f_3(x) จะได้ค่า yy ที่น้อยที่สุดที่เป็นไปได้ ซึ่งเท่ากับ 22

NOI18_Safety

โจทย์(OJ.UZ) มีตึกอยู่ NN ตึก โดยตึกหมายเลข ii มีความสูง AiA_{i} เมตร โดยตึกทั้งหมดนี้จะปลอดภัยก็ต่อเมื่อ AkAk+1h|A_{k} - A_{k + 1}| \le h สำหรับทุก k=1,2,3,,N1k = 1, 2, 3, \dots, N - 1 ในการทำให้ตึกทุกตึกปลอดภัยนั้นสามารถทำ 2 operation ที่ทำได้ คือ

  1. ลดความสูงของตึกหมายเลข kk ลง 11 เมตร
  2. เพิ่มความสูงของตึกหมายเลข kk อีก 11 เมตร

ต้องการหาจำนวน operation ที่น้อยที่สุดที่ทำให้ตึกทั้ง NN ตึกนี้ปลอดภัย

ปัญหาย่อย h=0h = 0

fi(x):=f_i(x) := จำนวน operation ที่น้อยที่สุดที่ทำให้ Ai=xA_i = x และ A1=A2==AiA_1 = A_2 = \dots = A_{i}
ดังนั้นจำนวน operation ในการทำให้ตึกที่ ii มีความสูง xx นั้นจะเท่ากับ Aix|A_{i} - x| ดังนั้น fi(x)=Aix+fi1(x)f_{i}(x) = |A_{i} - x| + f_{i - 1}(x)

ซึ่งสังเกตได้ว่าเหมือนกับปัญหา Median ด้านบน

ปัญหาเต็ม h109h \le 10^9

fi(x):=f_i(x) := จำนวน operation ที่น้อยที่สุดที่ทำให้ Ai=xA_i = x และ AjAi+1h|A_j - A_i + 1| \le h สำหรับ 1j<i1 \le j < i

ดังนั้น fi(x)=Aix+minxhkx+h{fi1(k)}f_{i}(x) = |A_{i} - x| + \min_{x - h \le k \le x + h} \{f_{i-1}(k)\}

gi(x):=g_i(x) := จำนวน operation ที่น้อยที่สุดที่ยังถูกเงื่อนไขเมื่อ Ai=xA_{i} = x
gi(x)=minxhkx+h{fi1(k)}g_i(x) = \min_{x - h \le k \le x + h} \{f_{i-1}(k)\} ทำให้ fi(x)=Aix+gi1(x)f_i(x) = |A_i - x| + g_{i - 1}(x)

สังเกตว่า f1(x)f_{1}(x) เป็นเส้นสีแดงและ g1(x)g_{1}(x) เป็นเส้นสีฟ้า เนื่องจาก g1(x)g_{1}(x) เป็นจำนวน operation ที่น้อยที่สุดที่ยังถูกเงื่อนไขเมื่อ A1=xA_{1} = x กล่าวคือเป็น f1(x)f_1(x) ที่ขยายไปทางซ้าย h-h และทางขวา +h+h

เมื่อใส่เส้นใหม่ไปเรื่อย ๆ ก็จะต้องขยายไปทางซ้าย h-h และขวา +h+h ทุกครั้งทำให้ใน Data Structure ของเราจำเป็นต้องเก็บค่า lazy ไว้ด้วย โดยอาจใช้ lazy set มาช่วยได้

Lazy Set Lazy set สามารถทำการ Implement ได้ด้วย std::set ธรรมดา (อาจใช้ std::multiset) โดยมีการเก็บค่า lazy ไว้ด้วยว่าค่านั้น ๆ จะถูกเปลี่ยนไปเท่าไหร่

หลักการของการใช้ของ lazy set คือ

ค่าจริง=ค่าที่อยู่ใน set+lazy \text{ค่าจริง} = \text{ค่าที่อยู่ใน set} + lazy

ในโจทย์ข้อนี้ไม่สามารถใช้ Lazy set เพียง 1 ตัวได้ เนื่องจากต้องการหาค่าที่อยู่ตรงกลางเนื่องจากกราฟเว้าลงมาจนตรงกลางของ Data Structure นั้นทำให้เกิดค่า min{fi(x)}\min\{f_i(x)\} และเนื่องจากต้องการหาตรงกลางของ Data Structure และในการทำ lazy ค่า hh จึงสามารถใช้ Lazy set 2 ตัวเพื่อเก็บจุดเปลี่ยนความชันฝั่งซ้ายและขวา

ข้อสังเกต ในการใส่เส้นใหม่แต่ละครั้งทำให้อาจค่าของ yy ที่ต่ำที่สุดเปลี่ยนไปได้แต่ค่า yy ไม่สามารถลดลงได้เนื่องจากเพิ่มเส้นใหม่ลงไปเรื่อย ๆ และจำนวน operation ที่ใช้ก็จะเพิ่มขึ้นหรือเท่าเดิมไม่สามารถลดลงได้

Solution Code

Reference