4409. 砍竹子
这天,小明在砍竹子,他面前有 n棵竹子排成一排,一开始第 i棵竹子的高度为 ℎi。
他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。
魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为 H,那么使用一次魔法可以把这一段竹子的高度都变为,其中 表示对 x 向下取整。
小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为 1。
输入格式
第一行为一个正整数 n,表示竹子的棵数。
第二行共 n个空格分开的正整数 ℎi,表示每棵竹子的高度。
输出格式
一个整数表示答案。
数据范围
对于 20%的数据,保证 1≤n≤1000,1≤hi≤10^6
对于 100%的数据,保证 1≤n≤2×105,1≤hi≤10^18
输入样例:
6
2 1 4 2 6 7
输出样例:
5
样例解释
其中一种方案:
2 1 4 2 6 7
→ 2 1 4 2 6 2
→ 2 1 4 2 2 2
→ 2 1 1 2 2 2
→ 1 1 1 2 2 2
→ 1 1 1 1 1 1
共需要 5步完成。
分析:
1.思路:
(1)每次取最大
(2)若有连续最大值,一起处理
2.how:
(1)如何取最大:优先队列(默认大根堆)
(2)如何判断是否存在连续区间:维护元素在原数组的位置(pair<value,id>)
想法一:设置两个数组pre[i]、pos[i]表示i前的位置和i后的位置(处理方式和十四届某道题神似)
(十四届整数删除的思路~)
想法二(大佬的想法):将id倒着存
即for(i:0~n-1) pair<value,n-i>
原因:当树高一样时,即value相等,排在队列前面的树也一定是原数组前面的树;那么,判断是否连续时,只须判断此时id是否 == pos - 1
注意点:
sqrt()和sqrtl()的区别:本题的数据范围是long long,所以处理平方和时要用sqrtl(),或者手写一个平方和公式也可~
代码:
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
typedef pair<long long,int> PII;
int main()
{
priority_queue<PII> q;
int n;
cin >> n;
for(int i = 0;i < n;i ++)
{
ll h;
cin >> h;
q.push({h,n-i}); //倒着存
}
ll pre = q.top().first; //1.取队首
int pos = q.top().second;
int ans = pre > 1 ? 1 : 0; //可能一开始树高都为1,特判一下
q.pop();
q.push({sqrtl(pre/2+1),pos});
while(!q.empty()) //2.进循环
{
PII p = q.top();
q.pop();
if(p.first == 1) continue; //3.树高为1,continue
if(p.first != pre || (p.first == pre && p.second != pos-1)) ans ++; //4.和前一棵树树高不相等 or 不连续
//5.处理树高
pre = p.first;
pos = p.second;
q.push({sqrtl(pre/2+1),pos});
}
cout << ans << endl;
return 0;
}