2011-0049

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

这个题的意思是关羽哥依次要过n个关卡,在过每个关卡的时候会扣一定数量的血Xi,每次打过一个关卡可以选择在该关卡回血,回血速度是Vi/天。关羽血有个上限Max,而且只能在当前关卡加血。
最后问你至少需要几天才能冲过全部关卡。
这个比较直观的可以想到贪心,如果需要加血,就尽可能挑实际回血速度最大的关卡加血。只不过在贪心的过程中需要注意的是关羽的血量是不能超过Max的,这导致有时关卡虽然Vi较大,但实际回血效果很小。
我用的一个优先队列才实现贪心,通过3个变量来维护这个优先队列。
先计算不加血经过每个关卡时,关羽的血量Ai。
用added表示当前已经加的血量,d表示当前加血的天数,pos标记有效关卡的起点。
Ai+added就是经过每个关卡时的实际血量了。
从头遍历,一旦需要回血,就pop出当前经过的所有关卡中,回血速度最大且在有效区间的。
判断其实际回血效果,一旦实际回血效果小于Vi的话,就更新Vi和pos。
若实际效果和Vi相同,就选择在该关卡休息。
{{{
#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;

struct node{
    int v,id;
    bool operator < (const node &tn) const{
            return v<tn.v;
    }
};

priority_queue<node> pq;
long long a[100005];

int main(){
    int i,j,k,x,pos,n;
    long long t,t2,added,max,d;
    node nd1,nd2;
    while(scanf("%d%lld",&n,&max)==2){
        a[0]=max;   d=added=0;  pos=0;
        for(i=1;i<=n;i++){
            scanf("%d%d",&x,&nd1.v);
            nd1.id=i;
            a[i]=a[i-1]-x;
            t=-a[i]-added+1;  //需要回血的量
            if(t>0){
                while(1){
                    t=-a[i]-added+1;
                    nd2=pq.top();
                    if(nd2.id<pos){pq.pop(); continue;}
                    if(t%nd2.v==0)  t2=t/nd2.v;
                    else    t2=t/nd2.v+1;
                    if((max-a[nd2.id]-added)/nd2.v>=t2){  //Vi等于实际效果
                        added+=t2*nd2.v;    d+=t2;  break;
                    }
                    else{  //Vi小于实际效果
                        t=(max-a[nd2.id]-added)/nd2.v;
                        pq.pop();
                        added+=t*nd2.v;
                        d+=t;
                        pos=nd2.id;
                        nd2.v=max-a[nd2.id]-added;
                        pq.push(nd2);
                    }
                }
            }
            pq.push(nd1);
        }
        while(!pq.empty()){pq.pop();}
        printf("%lld\n",d);
    }
}
}}}
By riversouther

这个题的意思是关羽哥依次要过n个关卡,在过每个关卡的时候会扣一定数量的血Xi,每次打过一个关卡可以选择在该关卡回血,回血速度是Vi/天。关羽血有个上限Max,而且只能在当前关卡加血。

最后问你至少需要几天才能冲过全部关卡。

这个比较直观的可以想到贪心,如果需要加血,就尽可能挑实际回血速度最大的关卡加血。只不过在贪心的过程中需要注意的是关羽的血量是不能超过Max的,这导致有时关卡虽然Vi较大,但实际回血效果很小。

我用的一个优先队列才实现贪心,通过3个变量来维护这个优先队列。

先计算不加血经过每个关卡时,关羽的血量Ai。

用added表示当前已经加的血量,d表示当前加血的天数,pos标记有效关卡的起点。

Ai+added就是经过每个关卡时的实际血量了。

从头遍历,一旦需要回血,就pop出当前经过的所有关卡中,回血速度最大且在有效区间的。

判断其实际回血效果,一旦实际回血效果小于Vi的话,就更新Vi和pos。

若实际效果和Vi相同,就选择在该关卡休息。

#include<iostream>
#include<stdio.h>
#include<queue>
using namespace std;
struct node{
    int v,id;
    bool operator < (const node &tn) const{
            return v<tn.v;
    }
};
priority_queue<node> pq;
long long a[100005];
int main(){
    int i,j,k,x,pos,n;
    long long t,t2,added,max,d;
    node nd1,nd2;
    while(scanf("%d%lld",&n,&max)==2){
        a[0]=max;   d=added=0;  pos=0;
        for(i=1;i<=n;i++){
            scanf("%d%d",&x,&nd1.v);
            nd1.id=i;
            a[i]=a[i-1]-x;
            t=-a[i]-added+1;  //需要回血的量
            if(t>0){
                while(1){
                    t=-a[i]-added+1;
                    nd2=pq.top();
                    if(nd2.id<pos){pq.pop(); continue;}
                    if(t%nd2.v==0)  t2=t/nd2.v;
                    else    t2=t/nd2.v+1;
                    if((max-a[nd2.id]-added)/nd2.v>=t2){  //Vi等于实际效果
                        added+=t2*nd2.v;    d+=t2;  break;
                    }
                    else{  //Vi小于实际效果
                        t=(max-a[nd2.id]-added)/nd2.v;
                        pq.pop();
                        added+=t*nd2.v;
                        d+=t;
                        pos=nd2.id;
                        nd2.v=max-a[nd2.id]-added;
                        pq.push(nd2);
                    }
                }
            }
            pq.push(nd1);
        }
        while(!pq.empty()){pq.pop();}
        printf("%lld\n",d);
    }
}

By riversouther