题目描述
小刚在玩JSOI提供的一个称之为“建筑抢修”的电脑游戏:经过了一场激烈的战斗,T部落消灭了所有z部落的入侵者。但是T部落的基地里已经有N个建筑设施受到了严重的损伤,如果不尽快修复的话,这些建筑设施将会完全毁坏。现在的情况是:T部落基地里只有一个修理工人,虽然他能瞬间到达任何一个建筑,但是修复每个建筑都需要一定的时间。同时,修理工人修理完一个建筑才能修理下一个建筑,不能同时修理多个建筑。如果某个建筑在一段时间之内没有完全修理完毕,这个建筑就报废了。你的任务是帮小刚合理的制订一个修理顺序,以抢修尽可能多的建筑。
输入输出格式
输入格式:
第一行是一个整数N,接下来N行每行两个整数T1,T2描述一个建筑:修理这个建筑需要T1秒,如果在T2秒之内还没有修理完成,这个建筑就报废了。
输出格式:
输出一个整数S,表示最多可以抢修S个建筑.
首先我们的、肯定是要求要求这个工人一开始就开始干,一直马不停蹄的干。这才是最优的。
可是要如何安排捏?
我们肯定是贪心的安排。如果当前时间比一个计划的最晚开始时间还早。我们肯定立马就干。
可是如果不是呢?
我们只有两种操作:1放弃 2.干
干的话我们就要放弃最多一个前面的任务,一定是最多。要是大于1个的话,我们为什么要干呢?
同样都是任务,我们放弃哪一个呢?肯定是放弃时间最长的好不好。 (这也就是反悔)
然后我们的算法流程就出来了
1.先将输入的数据按照自爆时间升序排序
2.扫描一遍,若满足当前时间小于最晚开始时间,累加
不满足,选一个已完成的任务中最大完成时间,将其所用时间返还,在判断是否合法
#include<algorithm>
#include<queue>
const int maxn=151000;
using std::sort;
using std::priority_queue;
priority_queue<long long>q;
struct node
{
long long end;
long long last;
};
node data[maxn];
bool compare(const node &a,const node &b)
{
return a.end<b.end;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&data[i].last,&data[i].end);
sort(data+1,data+1+n,compare);//排序
int ans=0;
long long now=0;
for(int i=1;i<=n;i++)
{
if(now<=data[i].end-data[i].last)//若当前时间 小于最晚开始时间
{
now+=data[i].last;//累加,因为是要马不停蹄的干吗
q.push(data[i].last);//然后放入
ans+=1;//只有在直接往后排是答案才可以增加,其余时间都是在替换
}
else
{
int nxt=q.top();//然后取出来一个当前完成任务中所需时间最大的任务
if(nxt>data[i].last&&now-nxt+data[i].last<=data[i].end)//比较
{//nxt>data[i].last是now-nxt+data[i].last<now然后移项后得到的
// now-nxt+data[i].last<=data[i].end是说在如此更爱后可以完成本次操作
q.pop();//删除堆顶
now=now-nxt+data[i].last;//更变时间
q.push(data[i].last);//放入
}
}
}
printf("%d",ans);//输出答案
return 0;
}
今天做完这两道带反悔操作的堆的贪心题后。
突然有一点小感触。
对于反悔操作,我们都是要找出他的逆操作来。然后我们还需要考虑什么时候使用逆操作。
然后我们一定要定序,只有定了序。我们才能按照一定的顺序贪心。这样的好处是,当你在扫描时,我们就可以使用一些数据结构来维护最值。而且在扫过的数据,你都可以拿来使用。
还有一定要从最基础的两个数据进行比较时分析。