题解:忍者队最大战斗力
本题有点难度,但是思考一下就会做了
题目大意
有 nnn 个忍者,每个忍者拥有战斗力 vvv,以及最多能接受的队伍人数上限 ttt。
选出一支队伍,队伍人数为 kkk,队伍中每一名忍者都必须满足 ti≥kt_i\ge kti ≥k,求队伍战斗力总和的最大值。
思路分析
核心观察
如果选定队伍人数 kkk,那么候选忍者只能是所有 ti≥kt_i \ge kti ≥k 的忍者;我们在这些忍者里面,选出战斗力最大的 kkk 个,此时总和就是这个 kkk 下能拿到的最优值。我们需要遍历所有可能的 kkk,找到最大总和。
直接枚举 kkk 暴力做复杂度太高,使用贪心+小根堆优化:
1. 贪心排序:把忍者按照承受上限 ttt 从大到小排序。
2. 遍历+小根堆:依次把忍者的战斗力加入小根堆,维护当前所有已经加入的忍者(他们的 ttt 都≥当前忍者的ttt)。
* 堆用来保存战斗力,小根堆堆顶是堆里最小的战斗力。
* 如果堆的大小 (sz>t)(sz>t)(sz>t):说明当前人数超过这一批忍者能承受的上限,删掉战斗力最小的忍者。
* 每次处理完一个忍者,更新全局答案(当前堆内总和就是合法队伍的战斗力)。
> 时间复杂度:O(nlogn)O(n\log n)O(nlogn)。排序 O(nlogn)O(n\log n)O(nlogn);每个元素入堆、出堆最多一次,堆操作O(logn)O(\log n)O(logn),可以通过 n=105n=10^5n=105。
> 空间复杂度:(O(n))(O(n))(O(n)),存储忍者信息 + 手写堆数组。
AC代码(手写堆,极致时空)
样例演示
输入:
1. 读入忍者:((t=1,v=100),(t=2,v=50),(t=2,v=60))((t=1,v=100),(t=2,v=50),(t=2,v=60))((t=1,v=100),(t=2,v=50),(t=2,v=60))
2. 按 ttt 降序排序:(((2,50)),((2,60)),((1,100)))(((2,50)),((2,60)),((1,100)))(((2,50)),((2,60)),((1,100)))
3. 遍历:
* 忍者((2,50))((2,50))((2,50)):堆[50],sum=50,sz=1 ≤ 2,ans=50
* 忍者((2,60))((2,60))((2,60)):堆[50,60],sum=(\boldsymbol{110}),sz=2 ≤2,ans=(\boldsymbol{110})
* 忍者((1,100))((1,100))((1,100)):堆[50,60,100] sum=210,sz=3>1,不断弹出最小值直到sz≤1。sum=100,ans保持(\boldsymbol{110})
4. 输出答案:(110)\boldsymbol{(\boldsymbol{110})}(110)
易错点
1. 读入顺序:输入每行是v t,pair存储要存(t,v),排序关键字是t,极易写反导致WA。
2. 数据范围:单个v可以到10910^9109,总和必须用long long,int会溢出。
3. 排序方向:必须按t降序,升序会混入不满足条件的忍者,答案错误。
4. 手写堆下标:堆数组从1开始,不要从0写,否则父子节点计算出错。
优化说明
* 使用手写小根堆替代STL优先队列,减少STL容器的内存冗余;提交结果:用时10ms,内存3.75MB,击败100%用户。
* 快读read()加速输入,搭配ios::sync_with_stdio(false);cin.tie(0)进一步压缩IO耗时。
结果展示