[JOIST 2025 Day3] 勇者比太郎 3 题解
很好的题目,使我的比太郎打怪兽。
为了方便解题,对比太郎打怪兽的过程进行时光倒流:从后往前考虑每个时间段,下文中的
先忽略
这里先给出结论。不妨认为
证明(自己搓的,如果有问题请在评论区指出):
这个值必然是答案的一个下界,因为只考虑前
i 个怪兽时,最多也只能打掉S_i 的 HP(怪兽在这之后会消失),产生剩下的罚分是不可避免的。下面证明这个值也是答案的上界。记
C_{l,r}=\sum\limits_{i=l}^rH_i 。不妨认为上述的值在i=k 时取最大值,那么:
- 对于
j>k ,有\ell\cdot C_{0,k}-S_k\ge \ell\cdot C_{0,j}-S_j ,变形得到S_j-S_k\ge\ell\cdot C_{j+1,k} ,所以后面的时间足够,剩余怪兽的 HP 均能全部解决,不会有更多的罚分。- 对于
j\le k ,考虑贪心策略的性质:比太郎处理怪兽1\sim k 的时间必然是一段前缀,只要这段前缀长度\ge S_k ,那么必然不会有更多的罚分。如果这段前缀长度<S_k ,分两种情况讨论
- 如果
S_{k-1}+\ell\cdot H_k<S_k\Leftrightarrow\ell\cdot C_{0,k}-S_k<\ell\cdot C_{0,k-1}-S_{k-1} ,这样就与一开始的假设(原式在i=k 处取到最大值)矛盾了!- 否则,考虑这个贪心策略中什么时候开始处理第
k 个怪兽,由于上面的条件不成立,又由于前缀长度<S_k ,所以处理第k 个怪兽必须在第S_{k-1} 个时间段或更早开始;于是又可以用上面的方法讨论第k-1 个怪兽……如此往复直到出现矛盾。可以证明矛盾一定出现,因为处理第1 个怪兽的开始时间不可能严格小于第一个时间段。综上,该结论成立。
接下来沿用证明中
考虑当不满足
最后对于
代码实现是较为简洁的。放代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef tuple<int,int,int> tpi;
inline int cd(int x,int y){
return x/y+!!(x%y);
}
inline int crs(pii x,pii y){
return cd(x.second-y.second,y.first-x.first);
} // 求出两条线段的交点的 x 坐标
inline pii operator +(pii x,pii y){
return make_pair(x.first+y.first,x.second+y.second);
}
inline pii operator -(pii x,pii y){
return make_pair(x.first-y.first,x.second-y.second);
}
inline pii operator *(pii x,int y){
return make_pair(x.first*y,x.second*y);
}
main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n,L,t; cin>>n>>L>>t;
vector<tpi> a(n);
vector<int> pl;
for(auto &[s,h,p]:a)
cin>>s>>h>>p,s=t-s,pl.emplace_back(p);
sort(a.begin(),a.end());
sort(pl.begin(),pl.end());
pl.erase(unique(pl.begin(),pl.end()),pl.end());
vector<pii> rs(L+2);
for(int i=0;i<pl.size();i++){
vector<pii> st={make_pair(0,0)};
int c=0;
for(auto [s,h,p]:a)
if(p>=pl[i]){
pii l(c+=h,-s);
while(st.size()>1&&crs(st[st.size()-2],st.back())>=crs(st.back(),l))
st.pop_back();
st.emplace_back(l);
} // 用单调栈把凸壳求出来
int w=pl[i]-(i?pl[i-1]:0);
for(int j=0;j<st.size();j++){
int l=0,r=L+1;
if(j)l=max(l,crs(st[j-1],st[j]));
if(j+1<st.size())r=min(r,crs(st[j],st[j+1]));
if(l<r)rs[l]=rs[l]+st[j]*w,rs[r]=rs[r]-st[j]*w;
} // 差分区间修改打标记
}
for(int i=1;i<=L;i++)
rs[i]=rs[i-1]+rs[i];
int q; cin>>q;
while(q--){
int lm,l=0,r=L; cin>>lm;
while(l<r){
int m=l+r+1>>1;
if(rs[m].first*m+rs[m].second<=lm)l=m;
else r=m-1;
} // 二分求解最大的级别
cout<<l<<'\n';
}
return 0;
}