programming.in.th · ข้อ 2039
คะแนนคือจำนวนคนที่ส่งไปคูณความเป็นผู้นำของผู้จัดการ ต้นไม้มีแสนคนและงบจำกัด จะเลือกผู้จัดการกับทีมยังไงให้ได้มากที่สุดโดยไม่ต้องลองทุกทาง
นิกายนินจาแห่งหนึ่งมีคนอยู่ N คน มีหัวหน้าใหญ่หนึ่งคน และคนที่เหลือมีหัวหน้าคนละหนึ่งคนพอดี
คำสั่งเรื่องงานเดินจากหัวหน้าลงไปหาลูกน้องเท่านั้น โครงแบบนี้คือต้นไม้ต้นหนึ่ง โดยหัวหน้าใหญ่เป็นราก
วันนี้เรากำลังรวบรวมนินจาไปส่งให้ลูกค้าหนึ่งราย กติกามีดังนี้
Mความพอใจของลูกค้าคือ จำนวนนินจาที่ถูกส่งไป คูณกับความเป็นผู้นำของผู้จัดการ งานของเราคือจัดให้ตัวเลขนี้สูงที่สุด
อินพุต / ขอบเขต / เอาต์พุต
N กับ M ต่อด้วย N บรรทัด
บรรทัดที่ i คือ Bi Ci Li ของนินจาคนที่ i
ได้แก่หมายเลขหัวหน้าของเขา ค่าจ้างของเขา และระดับความเป็นผู้นำของเขา ถ้า Bi = 0 แปลว่าคนนั้นคือหัวหน้าใหญ่
1 ≤ N ≤ 100,000, 1 ≤ M ≤ 1,000,000,000,
0 ≤ Bi < i, 1 ≤ Ci ≤ M, 1 ≤ Li ≤ 1,000,000,000 N ≤ 3,000 คิดเป็น 30% ของคะแนนเต็ม
เงื่อนไข Bi < i ในบรรทัดขอบเขตเป็นของแถมที่ใหญ่กว่าที่เห็น มันแปลว่าหัวหน้าของใครก็ตาม
มีหมายเลขน้อยกว่าลูกน้องเสมอ ต้นไม้ที่อ่านเข้ามาจึงเรียงมาให้เรียบร้อยแล้ว เก็บไว้ในใจก่อน เดี๋ยวได้ใช้
| Input | Output |
|---|---|
| 5 4 0 3 3 1 3 5 2 2 2 1 2 4 2 3 1 | 6 |
อ่านตัวอย่างนี้ยังไง
บรรทัดแรกบอกว่ามีนินจา 5 คน และงบ 4 อีก 5 บรรทัดถัดมาเป็นของนินจาคนที่ 1 ถึง 5 ตามลำดับ บรรทัดละสามตัวเลข คือหัวหน้า ค่าจ้าง ความเป็นผู้นำ อ่านทีละบรรทัดได้ว่า
ต้นไม้ที่ได้มีนินจา 1 อยู่บนสุด ใต้เขาคือนินจา 2 กับ 4 และใต้นินจา 2 คือนินจา 3 กับ 5
เอาต์พุตเป็นผลคูณ ไม่ใช่จำนวนคนหรือจำนวนเงิน คำตอบ 6 มาจากส่งไป 2 คน คูณความเป็นผู้นำ 3 ของผู้จัดการ
ลองตั้งผู้จัดการทีละคน แล้วซื้อคนที่ค่าจ้างถูกที่สุดในสายของเขาก่อน ได้ตามตารางนี้ (ในวงเล็บคือค่าจ้าง)
| ผู้จัดการ | คนในสาย | ซื้อได้ | คะแนน |
|---|---|---|---|
| 1 | 1 2 3 4 5 | 3 (2) 4 (2) | 2 x 3 = 6 |
| 2 | 2 3 5 | 3 (2) | 1 x 5 = 5 |
| 3 | 3 | 3 (2) | 1 x 2 = 2 |
| 4 | 4 | 4 (2) | 1 x 4 = 4 |
| 5 | 5 | 5 (3) | 1 x 1 = 1 |
ผู้จัดการที่ดีที่สุดคือนินจา 1 แต่ตัวเขาเองไม่ได้ไป สองคนที่ถูกที่สุดในสาย (ค่าจ้าง 2 กับ 2) ใช้งบ 4 หมดพอดี ไม่เหลือให้ค่าจ้าง 3 ของเขา
ส่วนนินจา 2 มีความเป็นผู้นำสูงที่สุดในนิกาย (5) แต่ตั้งเขาเป็นผู้จัดการแล้วได้แค่ 5 เพราะสายของเขามีคนเดียว
ใบ้
คะแนนเป็นผลคูณของสองอย่างที่ไม่รู้จักกันเลย ข้างหนึ่งขึ้นกับว่าเราส่งใครไปบ้าง อีกข้างขึ้นกับว่าใครเป็นผู้จัดการ ของแบบนี้มักคลี่ง่ายขึ้นมากถ้ายอมล็อกข้างหนึ่งไว้ก่อน
ลองสมมติว่ามีคนบอกคุณแล้วว่าผู้จัดการคือนินจาคนไหน คำถามที่เหลืออยู่คืออะไร และมันยากแค่ไหน แล้วถ้าต้องตอบคำถามนั้นให้ครบทั้ง 100,000 คน คำตอบของลูกน้องช่วยหัวหน้าได้ตรงไหนบ้าง
ห้าคน งบ 4 โจทย์เฉลยไว้แล้วว่าได้ 6 ลองไล่ให้เจอเองว่าใครเป็นผู้จัดการ แล้วส่งใครไปบ้าง
แตะนินจาที่จะให้เป็นผู้จัดการก่อน
ยังไม่ได้เลือกผู้จัดการ
กล่องขอบทองคือผู้จัดการ กล่องเขียวคือคนที่จะถูกส่งไป กล่องจาง ๆ คือคนที่ผู้จัดการสั่งไม่ถึง
ผู้จัดการสั่งได้เฉพาะคนที่อยู่ใต้เขาในต้นไม้ รวมตัวเขาเอง และเขาจะไปด้วยหรือไม่ไปก็ได้
ลองให้ครบทั้งสี่ชุดก่อนนะครับ ชุดที่ทำได้ยากที่สุดคือชุดสุดท้าย พอมีคำตอบในใจแล้วค่อยไปดูเฉลย
ที่มาของแนวคิดนี้
คะแนนเป็นผลคูณของสองอย่างที่ไม่ยุ่งกันเลย คือจำนวนคนที่ส่งไป กับความเป็นผู้นำของผู้จัดการ
เจอผลคูณแบบนี้ ผมล็อกข้างที่มีตัวเลือกน้อยกว่าไว้ก่อน ซึ่งที่นี่คือผู้จัดการ มีให้เลือกแค่ N แบบ
ก่อนลงมือเขียน ผมคูณเลขดูว่าลองผู้จัดการทีละคนแพงแค่ไหน ที่ N = 3,000 ได้ 9,000,000 ครั้ง ซึ่งสบาย
ที่ N = 100,000 ได้ 10,000,000,000 ครั้ง ชุดเต็มต้องให้ลูกน้องส่งงานที่ทำไว้ต่อขึ้นไปให้หัวหน้าใช้
สมมติว่ารู้แล้วว่าผู้จัดการคือนินจา m ตัวคูณ Lm (ความเป็นผู้นำของ m)
กลายเป็นค่าคงที่ทันที คะแนนขึ้นกับจำนวนคนที่ส่งไปได้อย่างเดียว
พอล็อกผู้จัดการ m แล้ว
งบ M ซื้อคนในสายของ m ได้มากที่สุดกี่คน
ตอบด้วยการเรียงค่าจ้างจากถูกไปแพง แล้วซื้อไปเรื่อย ๆ จนงบไม่พอ
ทำไมซื้อถูกก่อนไม่พลาด ดูตัวอย่างเล็ก ๆ งบ 5 มีคนค่าจ้าง 1, 2, 4 ถ้าเลือก 1 กับ 4 (รวม 5) ได้ 2 คน สลับ 4 ออกแล้วใส่ 2 ที่ถูกกว่าแทน ยังได้ 2 คน แต่จ่ายแค่ 3
กรณีทั่วไปก็เหมือนกัน ถ้าชุดที่ดีที่สุดมี k คนแต่ไม่ใช่ k คนที่ถูกที่สุด
ก็มีคนแพงอยู่ในชุดและคนถูกกว่าอยู่นอกชุด สลับกันได้เรื่อย ๆ จนได้ k คนที่ถูกที่สุดโดยไม่หลุดงบ
คำตอบของทั้งข้อคือค่ามากที่สุดของ (จำนวนคนที่ซื้อไหวในสายของ m) คูณ Lm
เมื่อไล่ m ครบทุกคน ที่เหลือคือเรื่องความเร็ว ไล่จากช้าไปเร็วได้สามขั้น
| ชุด | ขอบเขต | คะแนน |
|---|---|---|
| ชุดเล็ก | N ≤ 3,000 | 30% |
| ชุดเต็ม | N ≤ 100,000 | 70% |
วิธีที่ 1
2(จำนวนคนในสาย) ชุดต่อผู้จัดการ
อ่านนิยามตรง ๆ ถูกแน่นอน ไปได้ถึงราว 16 คน
ไม่ได้คะแนน ใช้เป็นตัวตรวจ
วิธีที่ 2
N × N = 9,000,000 ที่ N = 3,000
ผู้จัดการ N คน แต่ละคนเดินรายชื่อที่เรียงค่าจ้างไว้แล้วได้ถึง N คน วัดได้ไม่เกิน 0.02 วินาที
30%
วิธีเต็ม
ราว 3 × N × log N ≈ 5 ล้านก้าว ที่ N = 100,000
log N คือจำนวนครั้งที่หาร N ครึ่งหนึ่งจนเหลือ 1 (ราว 17 ที่แสนคน) ข้อสรุปเดิมทุกบรรทัด เปลี่ยนแค่ไม่ต้องนับใหม่ (ตอนที่ 2 และ 3) วัดได้ไม่เกิน 0.04 วินาที
100%
สิ่งที่แพงในวิธีที่ 2 คือการเริ่มนับใหม่ทุกครั้งที่เปลี่ยนผู้จัดการ ทั้งที่สายของหัวหน้าคนหนึ่ง คือสายของลูกน้องทุกคนต่อกัน บวกตัวเขาเอง งานที่ลูกทำไว้แล้วควรยกขึ้นไปใช้ต่อได้
ต่อจากนี้เรียกนินจาหนึ่งคนบนต้นไม้ว่าปม (node) และให้ทุกปมถือกองค่าจ้างของคนที่ซื้อไหวในสายของตัวเอง
กอง (heap) คืออะไร
ฮีป (heap) อ่านว่า "ฮีป" แปลว่ากอง ในหน้านี้คือกองตัวเลขที่ตัวมากที่สุดอยู่บนสุดเสมอ หยิบตัวบนสุดออกได้เร็ว และใส่ตัวใหม่ได้เร็ว
ขั้นตอนต่อปมมีสี่อย่าง
งานที่ทำกับกองมีสองอย่าง คือรวมสองกอง กับหยิบตัวแพงสุดออก
ฮีปสำเร็จรูปของ C++ (priority_queue) หยิบได้เร็ว แต่รวมสองกองตรง ๆ ไม่ได้ มีสองทางให้เลือก
priority_queue ต่อ แล้วรวมแบบเทกองเล็กลงกองใหญ่ (small-to-large) ทีละตัว
แต่ละคนถูกเทย้ายไม่เกินราว log N ครั้ง รวมราว N log² N ≈ 29 ล้านก้าว ยังทันใน 1 วินาที
แกะคำศัพท์ · leftist heap
leftist อ่านว่า "เลฟทิสต์" แปลว่า เอียงซ้าย จะมองมันเป็นกล่องดำก็ได้ ขอแค่รู้ว่ามันทำได้สองอย่าง คือรวมสองกอง กับหยิบตัวแพงสุดออก อย่างละราว log N ก้าว
ข้างในเป็นต้นไม้ที่ค่าของพ่อมากกว่าหรือเท่ากับค่าของลูกเสมอ ทุกปมจดระยะสั้นที่สุดจากตัวเองลงไปถึงช่องว่าง (dis ในโค้ด)
และลูกซ้ายต้องมี dis ไม่น้อยกว่าลูกขวา
ผลคือเส้นทางขวาสุดยาวไม่เกินราว log ของจำนวนปม การรวมสองกองเดินลงตามเส้นทางขวาของทั้งคู่อย่างเดียว ก้าวจึงน้อย
แล้วไล่ปมตามลำดับไหน เงื่อนไข Bi < i ในบรรทัดขอบเขตตอบให้แล้ว ลูกมีหมายเลขมากกว่าหัวหน้าเสมอ
ถ้าเดิน i จาก N ถอยลงมาถึง 1 ตอนถึงปมไหน ลูกทุกคนของปมนั้นยกกองขึ้นมาให้เรียบร้อยแล้ว
ไม่ต้องมี DFS (การเดินลงไปในต้นไม้ด้วยฟังก์ชันที่เรียกตัวเอง) และไม่ต้องกลัวต้นไม้ลึกแสนชั้นจนสแต็กแตก
// เดิน i จาก n ลงมา 1 ได้เลย เพราะโจทย์รับประกัน Bi < i
rt[i] = mergeHeap(rt[i], i); // ใส่ตัวเองเข้าไปในฮีปของตัวเอง
sum[i] += val[i]; cnt[i] += 1;
while (rt[i] && sum[i] > M) { // เกินงบก็ปลดคนที่ค่าจ้างแพงที่สุดทิ้ง
sum[i] -= val[rt[i]];
cnt[i] -= 1;
rt[i] = mergeHeap(lc[rt[i]], rc[rt[i]]);
}
ans = max(ans, (ll)cnt[i] * lead[i]); // ถ้า i เป็นผู้จัดการ ได้คะแนนเท่านี้
rt[par[i]] = mergeHeap(rt[par[i]], rt[i]); // แล้วยกทั้งกองขึ้นไปให้หัวหน้า
ลองเดินตัวอย่างในโจทย์ทีละปม เริ่มที่นินจา 5 แล้วไล่ลงมาถึง 1 ตาม Bi < i ลูกถึงคิวก่อนหัวหน้าเสมอ
ในตารางเขียนกองเรียงจากถูกไปแพงเพื่อให้อ่านง่าย ตัวขวาสุดคือตัวบนสุดของฮีป
เดินตัวอย่างในโจทย์ จาก i = 5 ลงมา 1 งบ 4
| ปม | รับกองจากลูก | ค่าจ้างตัวเอง | ปลดทิ้ง | กองที่เหลือ | คะแนนถ้าเป็นผู้จัดการ |
|---|---|---|---|---|---|
| นินจา 5 | ไม่มีลูก | 3 | · | [3] รวม 3 | 1 x 1 = 1 |
| นินจา 4 | ไม่มีลูก | 2 | · | [2] รวม 2 | 1 x 4 = 4 |
| นินจา 3 | ไม่มีลูก | 2 | · | [2] รวม 2 | 1 x 2 = 2 |
| นินจา 2 | จาก 3: [2] จาก 5: [3] | 3 | 3, 3 | [2] รวม 2 | 1 x 5 = 5 |
| นินจา 1 | จาก 2: [2] จาก 4: [2] | 3 | 3 | [2, 2] รวม 4 | 2 x 3 = 6 |
ปมที่ต้องปลดคนแพงคือนินจา 2 กับ 1 แถวของนินจา 1 ปลดค่าจ้าง 3 ของตัวเองทิ้ง ซึ่งก็คือเหตุผลที่ผู้จัดการอยู่บ้านในคำตอบ คะแนนสูงสุด 6 ตรงกับตัวตรวจ
ดูชุด "ผู้นำสูงสุด" ในมินิเกม (งบ 12) นินจา 3 ได้กอง 3, 4, 8 รวม 15 ซึ่งเกินงบ เขาหยิบ 8 ทิ้ง แล้วยกแค่ 3, 4 ขึ้นไปให้นินจา 1
ไม่มีใครเก็บคนที่ถูกทิ้งไว้ให้หัวหน้าเปลี่ยนใจทีหลัง ตอนนี้จะพิสูจน์ว่าหัวหน้าไม่มีวันต้องการคนนั้นคืน
ข้อที่ต้องพิสูจน์
หลังจัดการปม v เสร็จ กองของ v คือชุดเดียวกับที่ได้จากการซื้อคนถูกก่อนในสายของ v ทั้งสายตรง ๆ
(ค่าจ้างที่เท่ากันสลับกันได้ จำนวนคนและยอดเงินไม่เปลี่ยน)
พิสูจน์ไล่จากล่างขึ้นบน ตามลำดับที่โค้ดเดินจริง
Ci ≤ M จึงซื้อไหวเสมอ ข้อนี้จริง
v ที่ลูกทุกคนจริงตามข้อนี้แล้ว ให้ S คือชุดซื้อถูกก่อนของสาย v ทั้งสาย
คนใน S ที่มาจากสายของลูก c เป็นคนที่ถูกที่สุดของสาย c และรวมกันไม่เกินงบ
จำนวนของพวกเขาจึงไม่เกินจำนวนที่ c เก็บไว้ และทุกคนอยู่ในกองของ c S อยู่ครบในกองที่รวมจากลูกบวกตัว v หยิบคนแพงสุดออกจนพอดีงบ
ได้คนถูกที่สุดของกองนั้นเท่าที่ซื้อไหว ซึ่งก็คือ S พอดี เพราะ S เป็นคนถูกที่สุดของทั้งสายอยู่แล้ว และเพิ่มคนถัดไปอีกคนก็เกินงบ
โค้ดเดินจาก N ลงมา 1 ลูกถูกจัดการก่อนหัวหน้าเสมอ ข้อนี้จริงต่อขึ้นไปทุกชั้นจนถึงราก
คะแนนที่จดไว้ที่ทุกปมตรงกับคำตอบของตอนที่ 1 สำหรับผู้จัดการคนนั้นทุกตัว
ค่าใช้จ่ายรวมนับได้ตรง ๆ นินจาแต่ละคนถูกใส่เข้ากองครั้งเดียว ถูกหยิบออกได้มากสุดครั้งเดียว
และกองถูกยกขึ้นไปหาหัวหน้า N - 1 ครั้ง แต่ละอย่างใช้ราว log N ก้าว
รวมราว 3 × N × log N ≈ 5 ล้านก้าวที่ N = 100,000
เทียบกับ 10,000,000,000 ก้าวของวิธีที่ 2 ที่ N เดียวกัน ต่างกันหลักพันเท่า
ตรวจอะไรไปแล้วบ้าง
N = 100,000 ช้าสุด 0.04 วินาที ใช้ราว 7.5 MB
โค้ด subtask ที่ N = 3,000 ช้าสุด 0.02 วินาที ใช้ราว 3.9 MB
ข้อนี้มีสามชั้น ตอนที่ 1 เปลี่ยนผลคูณให้เหลือคำถามนับคน ตอนที่ 2 ทำให้ไม่ต้องนับใหม่ทุกผู้จัดการ และตอนที่ 3 คือเหตุผลที่ตอนที่ 2 ไม่ทำให้คำตอบผิด
ส่วนที่ยกไปใช้ข้ออื่นได้คือ เวลาโครงสร้างข้อมูลถูกยุบขึ้นไปตามต้นไม้ ของที่ถูกทิ้งกลางทางคือข้ออ้างที่ต้องพิสูจน์ และตัวตรวจที่ใช้เทียบต้องไม่รู้จักข้ออ้างนั้น
ในหน้านี้