programming.in.th · ข้อ 2004
ข้อที่ยกของจากบทผลรวมสะสมกับต้นไม้เฟนวิกมาใช้จริงทั้งชุด แกนของมันคือเอกลักษณ์บรรทัดเดียวที่ทำให้เงื่อนไขซึ่งผูกสองแกนไว้ด้วยกัน กลายเป็นเงื่อนไขที่ตรวจแยกกันได้ กับบทเรียนว่าทำไมท่าเดียวกันนี้ถึงช่วยอะไรไม่ได้เลยบนกระดานสามมิติ
เมอโกกับสลาฟโกจะเล่นเกมด้วยกัน เริ่มจากเลือกกระดานหนึ่งใบจากสามแบบ คือกระดานที่ช่องเรียงเป็นแถวเดียว (1 มิติ) เรียงเป็นตาราง (2 มิติ) หรือเรียงเป็นลูกบาศก์ (3 มิติ) จากนั้นเมอโกวางตุ๊กตาสัตว์ตัวจิ๋ว N ตัวลงไปในช่องต่าง ๆ ช่องหนึ่งซ้อนกันหลายตัวก็ได้
ระยะห่างระหว่างสองช่องคือจำนวนก้าวที่น้อยที่สุดที่ตุ๊กตาต้องเดินจากช่องหนึ่งไปอีกช่องหนึ่ง โดยหนึ่งก้าวคือขยับไปช่องที่ติดกัน ตุ๊กตาสองตัวได้ยินเสียงกันเมื่อช่องของมันห่างกันไม่เกิน D งานของสลาฟโกคือนับว่ามีตุ๊กตากี่คู่ที่ได้ยินเสียงกัน
เดินได้ทีละก้าวไปช่องที่ติดกันแปลว่าระยะห่างคือผลบวกของส่วนต่างในแต่ละแกน
บนกระดานสองมิติ ช่อง (5, 2) กับช่อง (8, 4) จึงห่างกัน |5 − 8| + |2 − 4| = 5 ก้าว ไม่ใช่ระยะเส้นตรง
ระยะแบบนี้มีชื่อว่า ระยะแมนฮัตตัน (Manhattan distance ตั้งตามผังถนนของเกาะแมนฮัตตันที่ตัดกันเป็นบล็อกสี่เหลี่ยม
จะข้ามจากบล็อกหนึ่งไปอีกบล็อกต้องเดินไปตามถนน ทะลุตึกไม่ได้)
อินพุต / ขอบเขต / เอาต์พุต
B ชนิดกระดาน, N จำนวนตุ๊กตา,
D ระยะที่ได้ยินถึงกัน และ M ขนาดกระดาน จากนั้นอีก N บรรทัด
บรรทัดละ B จำนวน คือพิกัดของตุ๊กตาแต่ละตัว แต่ละพิกัดมีค่าตั้งแต่ 1 ถึง M 1 ≤ B ≤ 3, 1 ≤ N ≤ 100 000, 1 ≤ D ≤ 100 000 000
ส่วน M ขึ้นกับชนิดกระดาน คือไม่เกิน 75 000 000 เมื่อ B = 1,
ไม่เกิน 75 000 เมื่อ B = 2 และไม่เกิน 75 เมื่อ B = 3
เวลา 4 วินาที หน่วยความจำ 150 เมกะไบต์
ค่า M ที่ต่างกันคนละโลกในสามกระดานไม่ใช่เรื่องบังเอิญ มันคือคำใบ้ที่โจทย์แปะไว้ให้ตั้งแต่ต้น
ว่าสามกระดานนี้ไม่ได้ตั้งใจให้แก้ด้วยวิธีเดียวกัน
| Input | Output |
|---|---|
| 1 6 5 100 25 50 50 10 20 23 | 4 |
| 2 5 4 10 5 2 7 2 8 4 6 5 4 4 | 8 |
| 3 8 10 20 10 10 10 10 10 20 10 20 10 10 20 20 20 10 10 20 10 20 20 20 10 20 20 20 | 12 |
ตัวอย่างที่สามคือแปดมุมของลูกบาศก์ที่มีด้านยาว 10 คู่ที่ห่างกันพอดี 10 คือคู่ที่อยู่ปลายขอบเดียวกัน ลูกบาศก์มีขอบ 12 เส้น คำตอบจึงเป็น 12 ส่วนคู่ที่อยู่ตรงข้ามกันบนหน้าเดียวกันห่าง 20 ก้าว เกินไปแล้ว
วิธีที่ตรงที่สุดคือไล่ดูทุกคู่ ซึ่งเขียนสามบรรทัดจบและถูกต้องแน่นอน ปัญหาคือจำนวนคู่
ที่ N = 100 000 มีคู่ทั้งหมด N(N−1)/2 คือราว 4,999,950,000 คู่
ห้าพันล้านครั้งของงานเบา ๆ ยังไงก็ไม่จบใน 4 วินาที และนี่คือเหตุผลที่โจทย์กำชับเรื่องจำนวนเต็ม 64 บิต
เพราะคำตอบเองก็ทะลุ int ได้ในกรณีที่ทุกคู่ได้ยินกันหมด
กระดานหนึ่งมิติพอมองออกไม่ยากว่าเรียงแล้วกวาดก็จบ ที่หนักคือกระดานสองมิติ เพราะเงื่อนไข
|Δx| + |Δy| ≤ D มีค่าสัมบูรณ์สองก้อนบวกกัน มันไม่ใช่เงื่อนไขที่แยกเป็นแกน x อย่างหนึ่งแกน y อย่างหนึ่งได้
บริเวณที่ได้ยินถึงกันจึงไม่ใช่กรอบสี่เหลี่ยมที่โครงสร้างข้อมูลทั่วไปชอบ แต่เป็นสี่เหลี่ยมข้าวหลามตัดที่วางเอียงอยู่กลางตาราง
ใบ้
ถ้ารูปที่ได้มันเอียง แล้วเราไม่ชอบของเอียง มีอะไรที่ทำกับระบบพิกัดได้บ้าง ลองเล่นกับตัวสาธิตข้างล่างดู แล้วสังเกตว่าตอนกดหมุน ขอบเขตที่เคยเอียงกลายเป็นอะไร
นี่คือกระดานจากตัวอย่างที่สอง กดที่ตุ๊กตาตัวไหนก็ได้เพื่อเลือกมัน พื้นที่สีเขียวคือช่องทั้งหมดที่ได้ยินเสียงตัวนั้น เลื่อนแถบเพื่อเปลี่ยนค่า D แล้วกดปุ่มหมุนเพื่อดูกระดานเดิมในมุมใหม่
ลองเล่นดูก่อนนะครับ พอมีคำตอบในใจแล้ว ค่อยไปดูเฉลย
เรียงตำแหน่งจากน้อยไปมากก่อน แล้วเดินตัวชี้ i ไปทีละตัว พร้อมลาก j ตามหลัง
โดยให้ j เป็นตัวซ้ายสุดที่ยังได้ยินเสียงตัวที่ i ตุ๊กตาทุกตัวที่อยู่ระหว่าง
j กับ i ก็ได้ยินตัวที่ i หมด เพราะมันอยู่ใกล้กว่า จึงบวกทีเดียว i − j คู่
ที่สำคัญคือ j เดินหน้าอย่างเดียว ไม่เคยถอยกลับ เพราะพอ i ขยับไปขวา
ขอบซ้ายของหน้าต่างก็ขยับไปขวาตามเสมอ ทั้งลูปนอกและลูปในรวมกันจึงเดินไม่เกิน 2N ก้าว
งานหนักที่สุดของกระดานนี้คือการเรียง
// กระดานหนึ่งมิติ: เรียงแล้วเดินสองตัวชี้
sort(x.begin(), x.end());
ll ans = 0;
int j = 0;
for (int i = 0; i < N; i++) {
while ((ll)x[i] - x[j] > D) j++; // j เดินหน้าอย่างเดียว ไม่เคยถอยกลับ
ans += i - j; // ทุกตัวตั้งแต่ j ถึง i-1 อยู่ในระยะได้ยิน
}
กองในตัวอย่างที่หนึ่งคือ 25 50 50 10 20 23 เมื่อ D = 5 เรียงแล้วได้
10 20 23 25 50 50 กดถัดไปเพื่อดูสองตัวชี้เดิน
| ช่อง | 10 | 20 | 23 | 25 | 50 | 50 | คู่สะสม |
|---|---|---|---|---|---|---|---|
| ตัวชี้ | · | · | · | · | · | · | 0 |
ช่องสีทองคือตัวที่ i ช่องสีฟ้าคือช่วงตั้งแต่ j ถึงตัวก่อนหน้า i ซึ่งคือคู่ใหม่ที่เพิ่งนับได้
คำตอบสุดท้ายคือ 4 คู่ ตรงกับตัวอย่างในโจทย์
ที่มาของแนวคิดนี้
ผมไม่ได้นึกถึงการหมุนขึ้นมาเองลอย ๆ มันมาจากการถามว่าทำไมกระดานหนึ่งมิติถึงง่าย คำตอบคือเงื่อนไขของมันพูดถึงแกนเดียว พอเรียงแล้ว ทุกอย่างกลายเป็นช่วงต่อเนื่องช่วงเดียว คำถามถัดไปจึงเขียนตัวเองว่า ต้องทำยังไงให้สองมิติกลายเป็นแบบนั้นบ้าง
ที่ค้างอยู่คือรูปร่าง เงื่อนไข |Δx| + |Δy| ≤ D วาดออกมาเป็นสี่เหลี่ยมข้าวหลามตัดที่วางเอียง
ซึ่งไม่มีโครงสร้างข้อมูลตัวไหนชอบ ส่วนรูปที่ทุกตัวชอบคือกรอบสี่เหลี่ยมที่ขนานกับแกน
พอตั้งโจทย์ได้ว่าอยากได้ข้าวหลามตัดให้กลายเป็นกรอบสี่เหลี่ยม สิ่งที่ต้องหาก็เหลืออย่างเดียวคือการหมุน 45 องศา
และเงื่อนไขก็เปลี่ยนจากผลบวกของค่าสัมบูรณ์ เป็นค่าที่มากกว่าของสองแกน ซึ่งแยกเป็นแกนใครแกนมันได้แล้ว
ก่อนเขียนโค้ดผมตรวจความเท่ากันของสองเงื่อนไขนี้ด้วยเครื่องก่อน สุ่มจุดสองจุดกับค่า D
รวม 200,000 ชุด แล้วเทียบว่าสูตรเดิมกับสูตรหลังหมุนตอบเหมือนกันทุกชุดไหม ตรงกันทุกชุด
รวมกรณีขอบอย่าง D = 0 และจุดที่ทับกันพอดี
บทเรียนที่ยกไปข้ออื่นได้คือ เวลาติดกับเงื่อนไขที่พัวพันกันสองแกน ให้ลองวาดบริเวณที่มันอนุญาตออกมาก่อน ถ้ารูปที่ได้ไม่ใช่กรอบสี่เหลี่ยม คำถามที่ควรถามคือมีระบบพิกัดไหนที่ทำให้มันเป็นกรอบสี่เหลี่ยม ไม่ใช่ไปหาโครงสร้างข้อมูลที่รับรูปเอียง ๆ ได้
ปัญหาของสองมิติคือค่าสัมบูรณ์สองก้อนบวกกัน ทางออกมาจากเอกลักษณ์ทางพีชคณิตบรรทัดเดียวที่เปลี่ยน "บวกกัน" ให้กลายเป็น "เลือกตัวที่มากกว่า"
ตรวจได้ง่าย ๆ ด้วยการแยกสองกรณี ถ้า a กับ b เครื่องหมายเดียวกัน
|a + b| จะเท่ากับ |a| + |b| พอดี ส่วน |a − b| จะเล็กกว่า
ถ้าคนละเครื่องหมายก็สลับบทบาทกัน ตัวที่มากกว่าจึงเท่ากับ |a| + |b| เสมอ
เอาเอกลักษณ์นี้ไปใส่กับ a = Δx และ b = Δy แล้วตั้งพิกัดใหม่
จะได้ว่า Δu = Δx + Δy และ Δv = Δx − Δy พอดี ระยะแมนฮัตตันจึงเขียนใหม่ได้เป็น
ด้านขวาคือระยะเชบีเชฟ (Chebyshev distance อ่านว่า "เชบีเชฟ" ตั้งตามชื่อ Pafnuty Chebyshev
นักคณิตศาสตร์ชาวรัสเซีย) ซึ่งแปลว่า "ห่างกันในแกนที่ห่างที่สุดไม่เกินเท่าไร" และนั่นคือเงื่อนไขที่
แยกเป็นแกนได้ คือ |Δu| ≤ D และ |Δv| ≤ D พร้อมกัน
แปลเป็นภาพคือสิ่งที่คุณเพิ่งกดดูในตัวสาธิต สี่เหลี่ยมข้าวหลามตัดในระบบพิกัดเดิม คือสี่เหลี่ยมจัตุรัสตรง ๆ ในระบบพิกัด
(u, v) การหมุน 45 องศาไม่ได้ทำให้ปัญหาง่ายขึ้นด้วยเวทมนตร์ มันแค่ทำให้เงื่อนไขที่พัวพันกันสองแกน
กลายเป็นเงื่อนไขที่ตรวจแยกกันได้
ที่เหลือเป็นท่ามาตรฐาน เรียงตุ๊กตาตาม u แล้วกวาดไปทางขวา เก็บเฉพาะตัวที่ u ห่างไม่เกิน D
ไว้ใน BIT (Binary Indexed Tree หรือที่เรียกกันว่า Fenwick tree คือต้นไม้ที่ตอบคำถาม
"ในช่วงนี้มีของกี่ชิ้น" และแก้ไขค่าได้ ในเวลา log) โดยใช้ v เป็นดัชนี
ถึงตัวไหนก็ถามว่ามีกี่ตัวที่ v อยู่ในช่วง [v − D, v + D]
ถ้าคำว่าท่ามาตรฐานยังไม่คุ้นมือ เครื่องมือชุดนี้อยู่ในบทปูพื้นฐาน
ผลรวมสะสมกับต้นไม้เฟนวิก ซึ่งให้กฎตัดสินด้วยว่าเมื่อไรผลรวมสะสมเฉย ๆ ก็พอ
// กระดานสองมิติ: หมุนพิกัดก่อน แล้วกวาดตาม u พร้อมนับ v ด้วย BIT
vector<pair<int,int>> q(N);
for (int i = 0; i < N; i++)
q[i] = {p[i].first + p[i].second, p[i].first - p[i].second + M}; // (u, v)
sort(q.begin(), q.end());
BIT bit;
bit.init(2 * M + 2);
ll ans = 0;
int j = 0;
for (int i = 0; i < N; i++) {
while ((ll)q[i].first - q[j].first > D) { bit.add(q[j].second, -1); j++; }
ans += bit.range((int)max<ll>(1, q[i].second - D),
(int)min<ll>(2 * M + 1, q[i].second + D));
bit.add(q[i].second, 1);
}
ตัวชี้ j ยังเดินหน้าอย่างเดียวเหมือนกระดานหนึ่งมิติ ต่างกันแค่ตอนมันเดินผ่านใคร
ต้องถอนคนนั้นออกจาก BIT ด้วย ทั้งหมดเป็น O(N log M)
โน้ต: ทำไมต้อง +M ตอนคำนวณ v
v = x − y ติดลบได้ ตั้งแต่ 1 − M ถึง M − 1 แต่ BIT ใช้ดัชนีติดลบไม่ได้
เลยบวก M เข้าไปให้ทุกค่าขยับมาอยู่ในช่วง 1 ถึง 2M − 1
ส่วน u ไม่ต้องเลื่อนเพราะเป็นบวกอยู่แล้ว
พอถึงสามมิติ คำถามแรกคือหมุนอีกทีได้ไหม คำตอบคือไม่ได้ การเปลี่ยนระยะแมนฮัตตันเป็นระยะเชบีเชฟด้วยการหมุนแบบนั้น ใช้ได้เฉพาะสองมิติ พอขึ้นสามมิติ รูปทรงของ "ขอบเขตการได้ยิน" กลายเป็นทรงแปดหน้า ซึ่งไม่มีการหมุนไหนดัดให้เป็นกล่องได้
แต่โจทย์ยื่นของอีกอย่างมาให้แทน คือ M ≤ 75 กระดานสามมิติทั้งใบมีช่องแค่
75 × 75 × 75 คือราวสี่แสนช่อง เล็กกว่าจำนวนตุ๊กตาไม่กี่เท่า นี่คือใบอนุญาตให้เราทำตารางค้างไว้ได้ทั้งกระดาน
แผนคือซอยกระดานเป็นชั้นตามแกน z แล้วมองทีละคู่ชั้น ถ้าตุ๊กตาตัวหนึ่งอยู่ชั้น z
และเราสนใจตัวที่อยู่ชั้น z' ระยะทางในแกน z ถูกใช้ไปแล้ว |z − z'| ก้าว
ที่เหลือคืองบที่ใช้ได้บนระนาบ xy คือ r = D − |z − z'| ถ้างบติดลบก็ข้ามชั้นนั้นไป
ภายในชั้นเดียวปัญหากลับไปเป็นสองมิติล้วน จึงหมุน 45 องศาแบบเดิมได้อีก ขอบเขต
|Δx| + |Δy| ≤ r กลายเป็นกรอบสี่เหลี่ยมในพิกัด (u, v)
และเพราะกระดานเล็ก เราทำผลรวมสะสมสองมิติเก็บไว้ล่วงหน้าทุกชั้นได้เลย
หลังจากนั้นการถามว่า "ในกรอบนี้ของชั้นนี้มีตุ๊กตากี่ตัว" ใช้การบวกลบสี่ครั้ง จบในเวลาคงที่
// กระดานสามมิติ: M เล็กมาก ทำผลรวมสะสมสองมิติเก็บไว้ทุกชั้น z
for (auto& a : p) {
int u = a[0] + a[1], v = a[0] - a[1] + M;
pre[a[2]][at(u, v)]++; // นับตัวลงชั้นของมันก่อน
}
for (int z = 1; z <= M; z++) { // แล้วค่อยทำผลรวมสะสมทีละชั้น
auto& g = pre[z];
for (int u = 1; u <= U + 1; u++)
for (int v = 1; v <= V + 1; v++)
g[at(u, v)] += g[at(u - 1, v)] + g[at(u, v - 1)] - g[at(u - 1, v - 1)];
}
ll ans = 0;
for (auto& a : p) {
int u = a[0] + a[1], v = a[0] - a[1] + M, z = a[2];
for (int zz = max(1LL, z - D); zz <= min<ll>(M, z + D); zz++) {
ll r = D - abs(zz - z); // ระยะที่เหลือให้ใช้บนระนาบ xy
if (r > 2 * M) r = 2 * M;
ans += box(zz, u - (int)r, v - (int)r, u + (int)r, v + (int)r);
}
}
ans = (ans - N) / 2; // ตัวเองถูกนับด้วย และทุกคู่ถูกนับสองครั้ง
งานทั้งหมดคือตุ๊กตา N ตัว คูณจำนวนชั้นไม่เกิน M = 75 ชั้น
คือราวเจ็ดล้านครั้งของการบวกลบสี่ตัว ส่วนตารางที่ต้องเก็บคือ 75 ชั้น ชั้นละราว 150 × 150 ช่อง
รวม 6.8 เมกะไบต์ จากลิมิต 150 เมกะไบต์
โน้ต: ทำไมต้องลบ N แล้วหารสอง
ตอนไล่ถามทีละตัว ตัวที่กำลังถามอยู่ในกรอบของตัวเองด้วย (ระยะศูนย์) จึงถูกนับเกินมา N ครั้ง
และคู่ที่ได้ยินกันจริงถูกนับสองครั้ง คือครั้งหนึ่งจากแต่ละฝ่าย คำตอบจึงเป็น (รวม − N) / 2
| กระดาน | เคสที่ใช้วัด | เวลา (มิลลิวินาที) |
|---|---|---|
| กระดาน 1 มิติ | N = 100 000, M = 75 000 000, D = 100 000 000 | 32 |
| กระดาน 2 มิติ | N = 100 000, M = 75 000, D = 500 | 51 |
| กระดาน 3 มิติ | N = 100 000, M = 75, D = 100 | 80 |
steady_clock คร่อมตั้งแต่ต้น main ถึงตอนพิมพ์คำตอบ จึงรวมเวลาอ่านอินพุตด้วย
เคสพวกนี้สุ่มพิกัดเต็มขอบเขตของแต่ละกระดาน
เฉลยของข้อนี้จัดการมิติที่สามด้วยการหมุนกระดาน ซึ่งใช้ได้เพราะรูปของโจทย์เอื้อให้ทำ แต่มีท่ามาตรฐานสำหรับปัญหารูปนี้ที่ใช้ได้โดยไม่ต้องพึ่งความพอดีของโจทย์ และคุ้มที่จะรู้เพราะมันเป็นท่าประจำของทุกโจทย์ที่ต้องนับคู่ที่เล็กกว่ากันครบทุกมิติ
ท่านั้นชื่อ CDQ divide and conquer และแนวคิดของมันคือลดจำนวนมิติ ด้วยการแบ่งครึ่ง ไม่ใช่ด้วยการหมุนหรือการเปลี่ยนพิกัด
ปัญหาคือนับคู่ที่ x เล็กกว่า และ y เล็กกว่า และ z เล็กกว่า
พร้อมกันทั้งสามมิติ เริ่มด้วยการเรียงตาม x แล้วแบ่งอาเรย์เป็นสองครึ่ง
ตรงนี้คือหัวใจ คู่ที่เราต้องนับแบ่งได้เป็นสามพวก คู่ที่อยู่ในครึ่งซ้ายทั้งคู่
คู่ที่อยู่ในครึ่งขวาทั้งคู่ และคู่ที่คร่อมสองครึ่ง สองพวกแรกคือปัญหาเดิมที่เล็กลง จึงเรียกซ้ำได้
ส่วนพวกที่สามมิติแรกไม่ต้องเช็คอีกเลย เพราะทุกจุดในครึ่งซ้ายมี
x เล็กกว่าทุกจุดในครึ่งขวาอยู่แล้วโดยการเรียง
พอมิติแรกหายไป งานที่เหลือคือปัญหาสองมิติ ซึ่งเราแก้ได้ด้วยท่าที่บทนี้ใช้อยู่แล้ว
คือกวาดตาม y พร้อมถามเฟนวิกบน z
และเพราะการเรียกซ้ำคืนของที่เรียงตาม y มาให้แล้วทั้งสองครึ่ง
การกวาดจึงทำพร้อมกับการรวมกลับแบบ merge ได้เลย ไม่ต้องเรียงใหม่
ต้นทุนจึงเป็น O(n log² n) คือแบ่งครึ่ง log n ชั้น ชั้นละ
n log n จากเฟนวิก ที่ n = 200,000 ผมรันโค้ดข้างล่างจริง
ได้ 0.23 วินาที เทียบกับการไล่ทุกคู่ที่เป็นสองหมื่นล้านครั้ง
ข้อกำหนดที่ต้องบอกไว้ตรง ๆ
โค้ดข้างล่างต้องการให้ค่า x ไม่ซ้ำกัน เพราะการแบ่งครึ่งใช้
"ลำดับหลังเรียงตาม x" แทนการเทียบค่า x จริง
ถ้ามีค่าซ้ำ สองจุดที่ x เท่ากันอาจไปอยู่คนละครึ่งแล้วถูกนับ
ซึ่งผิดเงื่อนไขที่ต้องเล็กกว่าจริง กรณีมีค่าซ้ำต้องจับกลุ่มที่เท่ากันมาหักออกอีกชั้น
ผมไม่ใส่ส่วนนั้นไว้เพื่อให้โครงของท่าอ่านออกง่าย และชุดสุ่มที่ใช้ทดสอบก็ให้ x ไม่ซ้ำตามนั้น
// CDQ divide and conquer: นับคู่ที่เล็กกว่ากันครบทั้งสามมิติ
// ให้จุด n จุด แต่ละจุดมี (x, y, z) นับจำนวนคู่ที่ xi<xj และ yi<yj และ zi<zj
//
// ข้อกำหนดของโค้ดชุดนี้: ค่า x ต้องไม่ซ้ำกัน
// เพราะการแบ่งครึ่งใช้ "ลำดับหลังเรียงตาม x" แทนการเทียบ x จริง ถ้ามี x ซ้ำ
// สองจุดที่ x เท่ากันอาจไปอยู่คนละครึ่งแล้วถูกนับ ซึ่งผิดเงื่อนไข xi<xj
// กรณีมี x ซ้ำต้องจับกลุ่ม x เท่ากันมาหักออกอีกชั้น ซึ่งไม่รวมในโค้ดนี้
#include <bits/stdc++.h>
using namespace std;
struct P { int x, y, z; };
int NZ;
vector<int> fen;
void fupd(int i, int v) { for (++i; i <= NZ; i += i & -i) fen[i] += v; }
int fqry(int i) { int s = 0; for (++i; i > 0; i -= i & -i) s += fen[i]; return s; }
long long answer = 0;
vector<P> a, buf;
void cdq(int lo, int hi) {
if (hi - lo <= 1) return;
int mid = (lo + hi) / 2;
cdq(lo, mid);
cdq(mid, hi);
// ออกจากสองบรรทัดบน ทั้งสองครึ่งเรียงตาม y แล้ว รวมกลับแบบ merge พร้อมนับ
// ทุกจุดในครึ่งซ้ายมี x เล็กกว่าทุกจุดในครึ่งขวาแน่นอน มิติแรกจึงไม่ต้องเช็คอีก
int i = lo, j = mid, k = lo;
while (i < mid || j < hi) {
if (j >= hi || (i < mid && a[i].y < a[j].y)) {
fupd(a[i].z, 1); // จุดซ้ายที่ y เล็กกว่า ใส่ลงเฟนวิกไว้
buf[k++] = a[i++];
} else {
answer += fqry(a[j].z - 1); // จุดขวา ถามว่ามีซ้ายกี่ตัวที่ z เล็กกว่า
buf[k++] = a[j++];
}
}
for (int t = lo; t < mid; t++) fupd(a[t].z, -1); // ล้างเฟนวิกให้ว่างก่อนออกจากชั้นนี้
for (int t = lo; t < hi; t++) a[t] = buf[t];
}
int main() {
int n;
if (scanf("%d", &n) != 1) return 0;
a.resize(n);
buf.resize(n);
for (int i = 0; i < n; i++) scanf("%d %d %d", &a[i].x, &a[i].y, &a[i].z);
// ย่อ z ให้เป็นดัชนีติดกัน เฟนวิกจึงมีขนาดพอดีกับจำนวนค่าที่ต่างกันจริง
vector<int> zs;
for (const P& p : a) zs.push_back(p.z);
sort(zs.begin(), zs.end());
zs.erase(unique(zs.begin(), zs.end()), zs.end());
for (P& p : a) p.z = (int)(lower_bound(zs.begin(), zs.end(), p.z) - zs.begin());
NZ = (int)zs.size() + 2;
fen.assign(NZ + 2, 0);
sort(a.begin(), a.end(), [](const P& p, const P& q) { return p.x < q.x; });
cdq(0, n);
printf("%lld\n", answer);
return 0;
} |a| + |b| = max(|a+b|, |a−b|) คือทั้งหมดที่ข้อนี้ต้องการ มันเปลี่ยนเงื่อนไขที่พัวพันสองแกน
ให้กลายเป็นสองเงื่อนไขที่ตรวจแยกกันได้ ส่วนกระดานสามมิติที่หมุนไม่ได้ โจทย์ก็ชดเชยให้ด้วยกระดานที่เล็กจนทำตารางค้างไว้ได้
ในหน้านี้