ในข้อนี้ เราต้องการใส่สีลงในตาราง โดยจะค่อยๆเติมสี่เหลี่ยมผืนผ้าที่มีความเข้มแตกต่างกัน แต่รับประกันแน่ๆว่าชิดขอบล่าง วิธีการที่เป็นพื้นฐานที่สุดของข้อนี้ คือค่อยๆเติมสี่เหลี่ยมผืนผ้าทีละชิ้น โดยใช้เวลารวม ซึ่งเห็นได้ชัดว่าไม่ทันเวลาแน่นอน
เราสามารถสร้าง event point เพื่อไล่จากซ้ายไปขวาได้ ดังนี้ เราสังเกตว่าการสร้างสี่เหลี่ยมผืนผ้าตั้งแต่พิกัด ถึง ความสูง นั้น ความเข้ม สามารถ encode จากค่า เป็นค่า สองค่าคือ และ ต่อมาเราจะใช้สามสิ่งอันดับเหล่านี้แทน จุดต่างๆที่จะมีการเปลี่ยนแปลงสี
เราจะค่อยๆเลื่อนคอลัมน์ พิจารณาจาก เพิ่มขึ้นไปเรื่อย หากมี event point บน ปัจจุบัน เราจะทำการ update คอลัมน์ปัจจุบันตามที่ event point กล่าวไว้ (เช่น เมื่อ มี แสดงว่าคอลัมน์ปัจจุบันจะมีค่าสีเพิ่มขึ้น สำหรับแถวที่สูงไม่เกิน ต่อมาคอลัมน์ จะเหมือนกับคอลัมน์ หากไม่มีการเปลี่ยนค่าใดๆเกิดขึ้น)
เราจะทำได้ในเวลา โค้ดตัวอย่างดังต่อไปนี้
int N, T; // Defined in problem statement vector<tuple<int, int, int>> eventPoints; // (x, h, o) as defined above for (int i = 0; i < N; i++) { int s, h, w, o; // s, h, w, o as defined in problem statement eventPoints.push_back({s, h, o}); eventPoints.push_back({s + w, h, -o}); } sort(eventPoints.begin(), eventPoints.end()); int answer = 0; int currentColumn[1048576]; // currentColumn[i] := val at height i on the // current col int eventPtr = 0; for (int i = 1; i <= 4000000; i++) { if (eventPtr < eventPoints.size()) { int x, h, o; tie(x, h, o) = eventPoints[eventPtr]; while (x == i) { // While there are some eventPoints which belong to current x, process // them. for (int j = 1; j <= h; j++) currentColumn[j] += o; eventPtr++; tie(x, h, o) = eventPoints[eventPtr]; } } for (int j = 1; j <= 1000000; j++) { if (currentColumn[j] == T) { // Expected value answer++; } } }
ต่อมา เราสังเกตว่าไม่จำเป็นจะต้องไล่ ให้ครบทุกค่าจาก ถึง โดยเราสามารถไล่เฉพาะค่าที่มีอยู่ใน eventPoints
ได้ สมมติคอลัมน์ มีค่าที่ตรงตามเงื่อนไข (เท่ากับ ) อยู่ทั้งหมด ค่า เราก็สามารถมั่นใจได้ว่า ระหว่าง ไปจนถึง นั้น ค่าที่เท่ากับ ก็จะมีอยู่ ค่าเช่นเดิม ( แทนคอลัมน์ที่มี event point ใดๆ และ แทนคอลัมน์ที่มี event point ที่อยู่ถัดจาก ) เราจะสามารถดัดแปลงได้ดังนี้
int N, T; // Defined in problem statement vector<tuple<int, int, int>> eventPoints; // (x, h, o) as defined above vector<int> possibleXs; // Maintain a set of columns with at least one event point for (int i = 0; i < N; i++) { int s, h, w, o; // s, h, w, o as defined in problem statement eventPoints.push_back({s, h, o}); eventPoints.push_back({s + w, h, -o}); possibleXs.push_back(s); possibleXs.push_back(s + w); } sort(eventPoints.begin(), eventPoints.end()); sort(possibleXs.begin(), possibleXs.end()); possibleXs.resize(unique(possibleXs.begin(), possibleXs.end()) - possibleXs.begin()); // Sort and resolve distinct column positions int answer = 0; int currentColumn[1048576]; // currentColumn[i] := val at height i on the // current col int eventPtr = 0; int lastPos = 0; for (int i : possibleXs) { for (int j = 1; j <= 1000000; j++) { if (currentColumn[j] == T) { // Expected value answer += i - lastPos; } } lastPos = i; if (eventPtr < eventPoints.size()) { int x, h, o; tie(x, h, o) = eventPoints[eventPtr]; while (x == i) { // While there are some eventPoints which belong to current x, process // them. for (int j = 1; j <= h; j++) currentColumn[j] += o; eventPtr++; tie(x, h, o) = eventPoints[eventPtr]; } } }
จะลดเวลาเหลือเพียง
ต่อมาคือขั้นตอนสำคัญของข้อนี้ คือการพยายามทำการ update ค่า currentColumn
ในเวลาที่รวดเร็วกว่าความสูงของ event point นั้น วิธีอย่างเป็นทางการของการ optimize ส่วนนี้คือการใช้โครงสร้างข้อมูลประเภทต้นไม้ค้นหาทวิภาค (Binary Search Tree) ที่แต่ละปมเก็บผลรวมของสีเอาไว้ แต่วิธีดังกล่าวเป็นวิธีที่ยากที่จะเขียนใช้จริง วิธีต่อมาที่จะนำเสนอในที่นี้คือการใช้โครงสร้างข้อมูลที่เกินขอบเขตเนื้อหาระดับชาติ แต่เขียนง่ายกว่าวิธีดังกล่าว นั่นคือการใช้ Fenwick Tree หรือเรียกอีกอย่างว่า Binary Search Tree (จากงานวิจัย A new data strcture for culumulative frequency tables โดย Peter M. Fenwick) ซึ่งเป็นโครงสร้างข้อมูลยอดนิยมในการแก้โจทย์ปัญหาหลังระดับชาติ
โครงสร้างข้อมูลดังกล่าวจะรองรับการดำเนินการดังนี้ add(idx, amt)
แทนการเติมค่า ตั้งแต่ ช่อง idx
ไปจนถึงช่องสูงสุด (ในที่นี้จะใช้ตัวแปร H
ซึ่งมีค่า ตามโจทย์) โดยเติมไป amt
หรือเทียบเท่าการดำเนินการตามโค้ดตังต่อไปนี้
int value[1048576]; void add(int idx, int amt) { for (int i = idx; i <= H; i++) value[i] += amt; }
ต่อมาจะรองรับการดำเนินการ sum(idx)
แทนการหาผลรวม ตั้งแต่ ช่อง idx
ลงไปถึงช่องต่ำสุด (ในที่นี้คือช่องหนึ่ง) เทียบเท่าการดำเนินการตามโค้ดต่อไปนี้
int value[1048576]; int sum(int idx) { int ret = 0; for (int i = idx; i >= 1; i--) { ret += value[i]; } return ret; }
สังเกตว่าการดำเนินการทั้งสองทำงานในเวลา แต่หากใช้โครงสร้างข้อมูล Fenwick Tree จะลดเวลาเหลือเพียงอย่างละ ทั้งนี้ จะไม่แสดงวิธีการสร้าง Fenwick Tree ในข้อนี้ แต่จะหยิบมาใช้งานเลย
จากโค้ดข้างต้นเราจะเปลี่ยนจาก
while (x == i) { // While there are some eventPoints which belong to current x, process them. for (int j = 1; j <= h; j++) currentColumn[j] += o; eventPtr++; tie(x, h, o) = eventPoints[eventPtr]; }
เป็น
while (x == i) { // While there are some eventPoints which belong to current x, process them. add(1, o); // processed by the Fenwick Tree add(j + 1, -o); // processed by the Fenwick Tree eventPtr++; tie(x, h, o) = eventPoints[eventPtr]; }
ต่อมา จะยังมีขั้นตอนที่ต้องหาว่ามีค่าที่เท่ากับ อยู่กี่ค่าใน Fenwick Tree แต่เนื่องจากโจทย์จะเติมจากช่องล่างสุดขึ้นไปข้างบนเท่านั้น เราสามารถสังเกตได้ไม่ยากว่า ในคอลัมน์ใดๆ ค่าที่อยู่แถวล่างจะมีค่า ไม่น้อยไปกว่า ค่าที่อยู่แถวบนกว่า เราจึงสามารถใช้ Binary Search เพื่อหาว่า แถวที่ต่ำที่สุดที่มีค่าน้อยกว่าหรือเท่ากับ คือแถวไหน (จะแทนด้วย ) และ แถวที่สูงที่สุดที่มีค่ามากกว่าหรือเท่ากับ คือแถวไหน (จะแทนด้วย ) จะได้ว่าคำตอบของคอลัมน์ปัจจุบันคือ
โค้ดตัวอย่างแก้ไขจาก
for (int j = 1; j <= 1000000; j++) { if (currentColumn[j] == T) { // Expected value answer += i - lastPos; } }
เป็นดังนี้
int rlow = binarySearchLessEqual(T); // Implement Binary Search int rhigh = binarySearchMoreEqual(T); // Implement Binary Search if (sum(rlow) == T && sum(rhigh) == T) { // Recheck in case there is no T found answer += (rhigh - rlow + 1) * (i - lastPos); }
เท่านี้การทำงานทั้งหมดจะใช้เวลา
Bonus: มันจะมีวิธีที่ดีกว่านี้มั้ย? ลองคิดอัลกอริทึมที่ทำได้ในเวลา ดู