题目大意
题目描述
Capoo 的初始 rating 为 x = 0 x=0 x = 0 。
现在有 n n n 场比赛,对于第 i i i 场比赛,它的难度为 a i a_i a i ,隐藏分数为 b i b_i b i 。每场比赛最多只能参加一次。
只有当当前 rating x ≤ a i x \le a_i x ≤ a i 时,Capoo 才能参加第 i i i 场比赛。参加完该场比赛后,Capoo 的 rating 将会更新为 max ( b i , x ) \max(b_i, x) max ( b i , x ) 。
你可以自由安排参加这 n n n 场比赛的顺序,求 Capoo 最多能参加多少场比赛。
输入格式
第一行包含一个整数 n n n (1 ≤ n ≤ 1 0 6 1 \le n \le 10^6 1 ≤ n ≤ 1 0 6 )。
接下来的 n n n 行,每行包含两个整数 a i a_i a i 和 b i b_i b i (0 ≤ a i , b i ≤ 1 0 18 0 \le a_i, b_i \le 10^{18} 0 ≤ a i , b i ≤ 1 0 1 8 ),分别表示第 i i i 场比赛的难度和隐藏分数。
输出格式
输出一行一个整数,表示 Capoo 最多能参加的比赛场数。
样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 输入: 10 19 18 6 15 5 8 4 20 18 3 16 9 0 7 5 17 2 13 15 17 输出: 5
样例解释
注意,这个人比较喜欢挑战这个难题。
一种最多参加 5 5 5 场比赛的合法顺序如下:
初始时 x = 0 x = 0 x = 0 。
参加第 7 7 7 场比赛(a 7 = 0 , b 7 = 7 a_7=0, b_7=7 a 7 = 0 , b 7 = 7 ):满足 x ≤ a 7 x \le a_7 x ≤ a 7 (0 ≤ 0 0 \le 0 0 ≤ 0 ),参加后 x x x 更新为 max ( 0 , 7 ) = 7 \max(0, 7) = 7 max ( 0 , 7 ) = 7 。
参加第 5 5 5 场比赛(a 5 = 18 , b 5 = 3 a_5=18, b_5=3 a 5 = 1 8 , b 5 = 3 ):满足 x ≤ a 5 x \le a_5 x ≤ a 5 (7 ≤ 18 7 \le 18 7 ≤ 1 8 ),参加后 x x x 更新为 max ( 7 , 3 ) = 7 \max(7, 3) = 7 max ( 7 , 3 ) = 7 。
参加第 6 6 6 场比赛(a 6 = 16 , b 6 = 9 a_6=16, b_6=9 a 6 = 1 6 , b 6 = 9 ):满足 x ≤ a 6 x \le a_6 x ≤ a 6 (7 ≤ 16 7 \le 16 7 ≤ 1 6 ),参加后 x x x 更新为 max ( 7 , 9 ) = 9 \max(7, 9) = 9 max ( 7 , 9 ) = 9 。
参加第 10 10 1 0 场比赛(a 10 = 15 , b 10 = 17 a_{10}=15, b_{10}=17 a 1 0 = 1 5 , b 1 0 = 1 7 ):满足 x ≤ a 10 x \le a_{10} x ≤ a 1 0 (9 ≤ 15 9 \le 15 9 ≤ 1 5 ),参加后 x x x 更新为 max ( 9 , 17 ) = 17 \max(9, 17) = 17 max ( 9 , 1 7 ) = 1 7 。
参加第 1 1 1 场比赛(a 1 = 19 , b 1 = 18 a_1=19, b_1=18 a 1 = 1 9 , b 1 = 1 8 ):满足 x ≤ a 1 x \le a_1 x ≤ a 1 (17 ≤ 19 17 \le 19 1 7 ≤ 1 9 ),参加后 x x x 更新为 max ( 17 , 18 ) = 18 \max(17, 18) = 18 max ( 1 7 , 1 8 ) = 1 8 。
总计参加了 5 5 5 场比赛,可以证明这是最多能参加的比赛场数。
思路讲解
将所有比赛分成两组:
安全组 A :b i ≤ a i b_i \le a_i b i ≤ a i 。参加后 x ← max ( x , b i ) ≤ a i x \leftarrow \max(x, b_i) \le a_i x ← max ( x , b i ) ≤ a i ,也就是说参加这场比赛不会把 x x x 抬到超过它自身的门槛 a i a_i a i 。对后续的影响很温和。
危险组 B :b i > a i b_i > a_i b i > a i 。参加后 x x x 必然跳到 b i b_i b i (因为 b i > a i ≥ x b_i > a_i \ge x b i > a i ≥ x ),对后续影响大。
为什么这个分组有用?因为安全组内部,如果按 a i a_i a i 升序排列,全部都能参加 ——每次参加后 x ≤ a i x \le a_i x ≤ a i ,而下一场的 a a a 更大,必然满足条件。所以安全组是"稳赚不赔"的。
下一步应该关注:安全组全做了,危险组怎么尽可能多地塞进来 **?(把安全组给固定住)**
第三层:排序与交错——两组的最优合并策略
一下的这个规则就是,在上面情况下,B 不会影响 A ?(先不要去想 B 会不会生效 )
关键的交错规则:在做每一个危险组比赛 B j B_j B j ( b b b 值为 b j b_j b j )之前,先把安全组中所有 a i < b j a_i < b_j a i < b j 的比赛做完。
为什么?做完 B j B_j B j 后 x = b j x = b_j x = b j ,那些 a < b j a < b_j a < b j 的安全组比赛就再也做不了了。提前做掉它们不会抬高 x x x (因为安全组的 b ≤ a < b j b \le a < b_j b ≤ a < b j ),不影响 B j B_j B j 的可行性。
这样,安全组的比赛一场都不会浪费 ——它们总是在 x x x 被抬高之前被消化掉。最终的答案就是:
安全组全部 + 按上述交错策略能做的危险组数量 \text{安全组全部} + \text{按上述交错策略能做的危险组数量}
安全组全部 + 按上述交错策略能做的危险组数量
不难想到,危险组按照这个 b 从小到大排比较好,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 auto forward=[&]() -> bool { while (idx1<SZ (safeA) && idx2<SZ (dangerA) && safeA[idx1].a<dangerA[idx2].b) { cur_ability=max (cur_ability,safeA[idx1].b); ++idx1; } if (cur_ability<=dangerA[idx2].a) { cur_ability=max (cur_ability,dangerA[idx2].b); ++ans; } ++idx2; if (idx2<SZ (dangerA)) { return true ; } return false ; }; while (forward()) {}
AC代码
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 #include <bits/stdc++.h> #define all(vec) vec.begin(),vec.end() #define lson(o) (o<<1) #define rson(o) (o<<1|1) #define SZ(a) ((long long) a.size()) #define debug(var) cerr << #var <<" = [" <<var<<"]" <<"\n" ; #define debug1d(a) \ cerr << #a << " = [" ; \ for (int i = 0; i < (int)(a).size(); i++) \ cerr << (i ? ", " : "" ) << a[i]; \ cerr << "]\n" ; #define debug2d(a) \ cerr << #a << " = [\n" ; \ for (int i = 0; i < (int)(a).size(); i++) \ { \ cerr << " [" ; \ for (int j = 0; j < (int)(a[i]).size(); j++) \ cerr << (j ? ", " : "" ) << a[i][j]; \ cerr << "]\n" ; \ } \ cerr << "]\n" ; #define cend cerr<<"\n-----------\n" #define fsp(x) fixed<<setprecision(x) using namespace std;using ll = long long ;using ull = unsigned long long ;using DB = double ;using i128 = __int128;using CD = complex<double >;static constexpr ll MAXN = (ll)1e6 +10 , INF = (1ll <<61 )-1 ;static constexpr ll mod = 998244353 ; static constexpr double eps = 1e-8 ;const double pi = acos (-1.0 );ll lT,testcase; struct A_b { ll a,b; }; struct Cmp { bool operator () (const A_b &a,const A_b &b) const { if (a.a!=b.a) return a.a<b.a; return a.b>b.b; } }; struct Cmp2 { bool operator () (const A_b &a,const A_b &b) const { if (a.b!=b.b) return a.b<b.b; return a.a<b.a; } }; void Solve () { ll N; cin >> N; vector<A_b> safeA,dangerA; for (int i=1 ;i<=N;++i) { ll a,b; cin>>a>>b; if (a>=b) { safeA.push_back ({a,b}); }else { dangerA.push_back ({a,b}); } } ll ans=SZ (safeA); sort (all (safeA),Cmp ()); sort (all (dangerA),Cmp2 ()); ll idx1=0 ,idx2=0 ; ll cur_ability=0 ; #ifdef LOCAL for (int i=0 ;i<SZ (safeA);++i) { debug (i); debug (safeA[i].a); debug (safeA[i].b); } cend; for (int i=0 ;i<SZ (dangerA);++i) { debug (i); debug (dangerA[i].a); debug (dangerA[i].b); } cend; #endif auto forward=[&]() -> bool { while (idx1<SZ (safeA) && idx2<SZ (dangerA) && safeA[idx1].a<dangerA[idx2].b) { cur_ability=max (cur_ability,safeA[idx1].b); ++idx1; } if (cur_ability<=dangerA[idx2].a) { cur_ability=max (cur_ability,dangerA[idx2].b); ++ans; } #ifdef LOCAL debug (idx1); debug (idx2); debug (ans); debug (cur_ability); debug (dangerA[idx2].a); debug (dangerA[idx2].b); #endif ++idx2; if (idx2<SZ (dangerA)) { return true ; } return false ; }; while (forward()) { } cout<<ans<<"\n" ; } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); cout.tie (nullptr ); #ifdef LOCAL cout.setf (ios::unitbuf); #endif Solve (); return 0 ; }
心路历程(WA,TLE,MLE……)