P8170 题解
这题跟 P3826 [NOI2017] 蔬菜 在外观上差不多,如果再深入观察一下,会发现跟蔬菜这道题恰好相反。
即:每棵树尽量在前面被砍,且当前能算出的答案最大的值最应该被砍。
对于一棵树,只有最终答案和什么时候能被砍这两个东西有意义,需要维护。
考虑一个 naive 的想法,二分一个答案 ans,枚举每天每棵树的状态,先把每棵高度超过 ans 的树砍到小于等于 ans,如果还能剩余一些砍树的机会,则优先砍当前最高的树(除非最高的树都小于 x,砍不了)。
这个想法错在哪里?对于一棵树并不是一比 ans 大就砍是最优的,因为如果它下一次大于 ans 时有很多很多树都同时大于 ans,就可能砍不到它。虽然硬构造这样一组数据很困难,但由于此题有子任务捆绑所以并不能通过,而且时间复杂度并不优秀。
但据此可以想到一个正确的二分答案做法,即对于所有最终要超过 ans 的树,当前能砍则砍,按最终高度从高到低砍,这样是能够让砍掉的利益最大化的,因为既然最后要超过 ans,那无论如何总得砍,能早砍就早砍。
还可以想到一个更 naive 的贪心的做法,把所有树直接转化成其最终高度,不考虑日期,然后
问题就出在维护日期上。所以考虑给每棵树设一个限制:必须在第
然而每次砍了一棵树,这棵树又可能会掉下
考虑实现一个函数 Get(int h,int d,int c),表示当前这棵树的初始高度为
设
经过一通计算,可得
按照之前的 naive 贪心原则,每次选当前计算出的最终高度最高的树,并把其剪掉,找出这棵树记录的
对于每次选最终高度更高的树,可以用堆维护,如何计算
时间复杂度
若把所有树按照一次都不剪算出来的最终高度排序,每棵树如果都被剪了一次,在相对高度上是完全相同的,所以可以用队列维护被剪过的树(一定是单调队列),用数组初始排序维护最终高度从大到小没被剪过的树。
每次比对队列开头和数组当前位置,选择较大的一方进行操作,然后放入队列末尾。如果队列为空,直接用数组当前位置(当且仅当第一次操作才会出现这种情况)。否则如果用了数组当前位置,应以后用下一个位置(即数组每个下标只会被用一次,毕竟维护的是没有剪过的树)。
时间复杂度为
堆的代码
队列的代码
以上两份代码的 Get 函数都写错了,正确写法应该是:
int Get(int h,int d,int c)
{
if(d==0)
{
return h-c*X<X?M+1:1;
}
return max(1,((c+1)*X-h-1)/d+1);
}