Subtask 1 (14 คะแนน) และ
ในปัญหาย่อยนี้จะมีเพียงจุดเท่านั้น ดังนั้นหากเราสามารถตรวจสอบได้ว่าจุดใดบดบังกันก็สามารถลบจุดที่โดนบดบังออกได้เลย จุดหนึ่งจุดจะบดบังหรือโดนบดบังเป็นเส้นตรงที่ลากจาก และ ดังนั้นหากมีเส้นตรงสองเส้นที่่ความชันเท่ากันแล้ว จะมีหนึ่งในสองจุดที่ต้องนำออก ในการเก็บความชันของจุดก่อน ๆ สามารถใช้ data structure อย่าง std::set หรือ std::unordered_set เพื่อเพิ่มและตรวจ สอบความชันที่เคยใส่แล้วได้โดยการนำ ไปหารทั้ง และ ให้อยู่ในรูปของเศษส่วนอย่างต่ำก่อนใส่ใน set นั่นเอง
Time Complexity:
Subtask 2 (25 คะแนน)
เนื่องจาก เราสามารถพิจารณาช่วงที่โดนบดบังในแกน ได้ สังเกตว่าเส้นส้มหนึ่งเส้นจะตัดกับเส้นตรง ที่จุด และ ซึ่งมองเป็นช่วงที่จะโดนบดบังได้ ดังนั้นหากเราหาได้ว่าต้องเลือกช่วงให้มีเส้นส้มได้มากที่สุดอย่างไร ก็จะสามารถตอบได้ว่าต้องถอนเส้นส้มออกอย่างน้อยที่สุดกี่เส้น ซึ่งปัญหานี้ก็สามารถแก้ไขได้ด้วยอัลกอริทึมคลาสสิกอย่าง Interval Scheduling1 โดย Interval Scheduling เป็น greedy algorithm ที่จะทำการ sort ช่วงตามจุดจบและเลือกเส้นที่เลือกได้ทันที จากนั้นเมื่อพิจารณาช่วงถัด ๆ ไปก็จะพิจารณาว่าจุดเริ่มต้นซ้อนทับจุดสิ้นสุดของช่วงที่เคยเลือกไว้หรือไม่
Time Complexity:
Subtask 3 (26 คะแนน)
ข้อสังเกต #1: หากเลือกเส้นตรงหนึ่งเส้นจะมีช่วงที่ไม่สามารถเลือกได้เกิดขึ้น ซึ่งช่วงนั้นจะอยู่ระหว่างเส้นตรงที่ลากจาก ไปยัง และ โดยเราสามารถมองเป็นช่วงของความชันได้ ในที่นี้เรียกค่าความชันที่น้อยกว่าว่า "ความชันเริ่มต้น" และ เรียกค่าที่มากกว่าว่า "ความชันสิ้นสุด"
ปัญหาย่อยนี้สามารถแก้ได้ด้วยการทดลองทำตามเงื่อนไขโจทย์โดยตรงด้วยการเลือกสับเซตของเส้นส้มที่ต้องการเอาออกและตรวจสอบว่าเส้นส้มที่เหลืออยู่แต่ละเส้นตัดกันหรือไม่ (ใช้แนวคิดจากข้อสังเกต 1) เนื่องจากเรามีสับเซตที่เป็นไปได้ทั้งหมด สับเซต และการตรวจสอบว่าเส้นส้มเส้นหนึ่งตัดกับเส้นใด ๆ หรือไม่สามารถทำได้ใน ทำให้เราสามารถแก้ปัญหานี้ได้ใน ซึ่งเพียงพอสำหรับค่า
หมายเหตุ : ระวังช่วงที่มีความชันเริ่มต้นเท่ากับความชันสิ้นสุดของเส้นก่อนหน้า ไม่สามารถเลือกสองช่วงนั้น ๆ ได้
Time Complexity:
Subtask 4 (18 คะแนน)
ในปัญหาย่อยนี้สามารถใช้ Dynamic Programming ในการแก้ได้ นิยามความชันเริ่มต้นและความชันสิ้นสุดเป็นค่าน้อยกว่าและค่ามากกว่า ระหว่าง และ ตามลำดับ หากเราพิจารณาเส้นส้มจากค่าความชันเริ่มต้นที่เรียงจากน้อยไปมาก ในเส้นส้มลำดับที่ จะได้สมการของอาเรย์ ว่า
ทำให้เราสามารถคำนวณ ได้ใน สำหรับแต่ละเส้นส้ม และส่งผลให้แก้ปัญหาทั้งหมดได้ใน
Time Complexity:
Official Solution
หากเราสังเกตโครงสร้างของปัญหานี้ด้วยการใช้แนวคิดจากปัญหาย่อยที่ 2 และ 3 เราจะสามารถตรวจสอบได้ว่าเราเลือกเส้นส้มได้ทั้งหมดกี่เส้นด้วยอัลกอริทึมอย่าง Interval Scheduling1 บนความชันความชันเริ่มต้นและความชันสิ้นสุดได้ และได้ว่าจำนวนเส้นส้มที่น้อยที่สุดที่ต้องถอนจะเท่ากับ ลบด้วยจำนวนเส้นส้มที่มากที่สุดที่สามารถเลือกได้ ในปัญหานี้ได้ถูกออกแบบให้สามารถใช้ตัวแปรประเภท double เก็บข้อมูลได้ (การใช้ float จะทำให้คำตอบที่ผิด) อย่างไรก็ตามหากมีการเปรียบเทียบที่มีความแม่นยำมากกว่านี้ควรเก็บตัวแปรเป็น long long ของจำนวนเต็มและใช้วิธีการคูณไขว้เพื่อเทียบความชันในรูปเศษส่วนแทน โดยที่
เมื่อเรา assume ว่า
Time Complexity:
1ข้อมูลเพิ่มเติมของ interval scheduling สามารถศึกษาได้ที่ https://en.wikipedia.org/wiki/Interval_scheduling
Solution Code
#pragma GCC optimize("O3") #include<bits/stdc++.h> #define pii pair<int, int> #define pII pair<pii, pii> #define pll pair<long long, long long> #define ll long long #define ld long double #define st first #define nd second #define pb push_back #define INF INT_MAX #define sz(x) x.size() using namespace std; const int N = 1e5 + 10; pair<pll, pll> line[N]; bool cmp(pair<pll, pll> a, pair<pll, pll> b) { pll cur_a = a.nd; pll cur_b = b.nd; return cur_b.st*cur_a.nd < cur_a.st*cur_b.nd; } void solve() { int n; cin >> n; for (int i = 1; i <= n; ++i) { ll x, y, z, w; cin >> x >> y >> z >> w; if (y*z > w*x) swap(x, z), swap(y, w); line[i] = {{x, y}, {z, w}}; } sort(line+1, line+n+1, cmp); int cnt = n; pll cur = {1, -1}; for (int i = 1; i <= n; ++i) { auto [x, y] = line[i].st; if (cur.nd*x >= cur.st*y) continue; cnt--; cur = line[i].nd; } cout << cnt << '\n'; } int main() { ios_base::sync_with_stdio(0); cin.tie(NULL); int t = 1; // cin >> t; while (t--) { solve(); } }