- [SanhaiOJ大月赛]Round 2 Div2
T1 题解(deepseek)
- @ 2026-10-10 16:07:59
题意简述
有 个节点,每个节点有攻击门槛 和战后恢复 。
攻击第 个节点前,当前精力必须满足:
攻击后精力变化为:
可以任意安排攻击顺序,要求任意时刻精力不低于 。问初始精力至少是多少。
核心思路
每个节点的净收益为:
如果 ,也就是 ,说明打完后精力不会减少,甚至会变多,称为正收益节点。
如果 ,也就是 ,说明打完后精力会减少,称为负收益节点。
显然:
- 正收益节点应该尽量先打,因为它们会让精力变多;
- 负收益节点应该尽量后打,因为它们会让精力变少;
- 正收益节点内部,门槛低的先打;
- 负收益节点内部,恢复值 大的先打。
于是排序规则为:
- 先排正收益节点:
- 按 升序排序
- 后排负收益节点:
- 按 降序排序
- 如果 相同,按 升序排序
贪心正确性说明
1. 正收益节点一定放在负收益节点前面
假设有一个负收益节点 排在正收益节点 前面。
交换它们:
- 原来先打 ,精力减少,再打 ;
- 交换后先打 ,精力不减少甚至增加,再打 。
交换后,打 之前的精力只会更高,所以不会更难满足门槛。
因此所有正收益节点都应该排在负收益节点前面。
2. 正收益节点内部按 升序
两个正收益节点 ,若:
如果先打 ,则一开始就需要至少 的精力。
如果先打 ,因为 更小,更容易满足,而且打完后精力还会增加,再去打 会更轻松。
所以正收益节点应按门槛 从小到大排序。
3. 负收益节点内部按 降序
两个负收益节点 ,假设:
如果先打 ,打完虽然会消耗精力,但会恢复更多 ,留给后面节点更多精力。
如果先打 ,恢复的精力更少,后面打 时压力更大。
所以负收益节点应按恢复值 从大到小排序。
如何计算最少初始精力
排序后,我们按最优顺序模拟。
设:
cur表示当前精力;need表示需要额外补充的初始精力。
初始令:
依次处理每个节点:
如果当前精力不足以攻击:
那么就需要补充:
并且把当前精力补到 :
然后攻击该节点,精力变化:
最终 need 就是最少需要的初始精力。
为什么这样是对的?
因为每次当前精力不够时,缺多少就必须在初始精力里多补多少。
最后累计出来的 need,就是整个过程中所有缺口的最大值总和,也就是最小初始精力。
时间复杂度
排序时间复杂度:
模拟时间复杂度:
总时间复杂度:
空间复杂度:
参考代码
注:Panda写
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
struct Task{
int a,b;
};
int n;
Task a[N];
bool cmp(Task x,Task y){
bool chka=(x.a<=x.b);//x是否为正收益关卡
bool chkb=(y.a<=y.b);//y是否为正收益关卡
if(chka!=chkb) return chka>chkb;
if(chka) return x.a<y.a;//正收益关卡按门槛升序排列
else{
if(x.b!=y.b) return x.b>y.b;//负收益关卡按恢复值降序排列
return x.a<y.a;
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].a>>a[i].b;
sort(a+1,a+n+1,cmp);
int m=0,need=0;//m为当前生命值,need为需要补充补充的初始生命值
for(int i=1;i<=n;i++){
if(m<a[i].a){
need+=a[i].a-m;
m+=a[i].a-m;
}
m+=a[i].b-a[i].a;
}
cout<<need<<endl;
return 0;
}