Apprentice Learning Trajectory(铸剑,经典单调栈问题+贪心)
题目描述
有n个房子,第i个房子开放时间从li到ri,在这个房间内铸一把剑要ti的时间。同一时间只能在一个房子内铸剑,
同一时间只能铸一把剑,并且直到铸完才能离开。问你最多能铸多少剑
题目题解
这个题是一个经典的贪心问题,就是这个不能按照l[i]排序,要按照t[i]+l[i],对于每一个时间段,找到能在这个时间段铸剑的最小值,这个用单调栈维护每一个时间段铸剑的最小值
题目分析:对于一个时间戳 t 而言,只有两种情况,我们可以讨论一下:
如果 t 时刻没有房子开放,那么我们选择接下来首先可以铸完剑的房子显然是最优的,注意是首个可以铸完剑的房子,而不是首个可以铸剑的房子,换句话说也就是 l[ i ] + t[ i ] 最小的区间肯定是最优的,而不是 l[ i ] 最小的区间
如果 t 时刻有房子开放,那么我们显然在 t[ i ] 最小的房子里铸剑是最优的
基于此,这个题目就变成了一个贪心的问题,考虑如何贪心
因为数轴给的区间非常大,但是却非常的稀疏,所以我们并不需要枚举时间戳 t ,而是枚举每个区间的开始点和结束点就可以计算出相应的贡献
具体如何计算呢,因为上面的讨论中也提到了,按照 l[ i ] + t[ i ] 升序排序对于情况 1 来说一定是最优的,对于情况 2 最优的话,我们需要维护一个数据结构,满足在一堆数字中快速查找到其最小值,还必须要快速的插入和删除,因为对于每个端点来说都是需要更新的,所以不难想到用 set 来维护,这样每次插入删除都是 log 级别的,查询更是 O( 1 ) 级别的,又因为时间 t[ i ] 是可以重复出现的,所以我们选择用 multiset 进行维护铸剑时间 t[ i ] 的最小值
剩下的实现就好了, 维护一个 pre 指针代表上一次的结束位置,每次让 pre 贪心在数轴上跳,顺便维护一下贡献就好了,有点小细节可以看代码
Code
#include<iostream> #include<algorithm> #include<cstring> #include<set> using namespace std; typedef long long ll; const int maxn=3e6+100; struct node{ ll l,f,t; bool friend operator<(node x,node y){ if(x.l==y.l){ return x.f<y.f; } else{ return x.l<y.l; } } }save[maxn]; int n; multiset<ll>s; int main(){ cin>>n; for(int i=1;i<=n;i++){ ll l,r,t; scanf("%lld%lld%lld",&l,&r,&t); save[i]={l+t,0,t}; save[n+i]={r,1,t}; } sort(save+1,save+2*n+1); ll sum=0,tmp=0; ll ans=0; for(int i=1;i<=2*n;i++){ //cout<<save[i].l<<" "<<save[i].f<<" "<<save[i].t<<endl; if(!s.empty()){ ll z=*s.begin(); ll num=(save[i].l-tmp)/z; ans+=num; tmp+=num*z; } if(save[i].f){ s.erase(s.find(save[i].t)); } else{ if(tmp<=save[i].l-save[i].t){ ans++;tmp=save[i].l; } s.insert(save[i].t); } } cout<<ans<<endl; }