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;
}

 

posted @ 2021-10-06 21:27  lipu123  阅读(91)  评论(0)    收藏  举报