题目链接:
题目描述:
这天,小明在砍竹子,他面前有 n 棵竹子排成一排,一开始第 i 棵竹子的高度为 hi。
他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。
魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为 H,那么使用一次魔法可以把这一段竹子的高度都变为
⌊⌊H2⌋+1−−−−−−−√⌋,其中 ⌊x⌋ 表示对 x 向下取整。小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为 1。
输入格式 第一行为一个正整数 n,表示竹子的棵数。
第二行共 n 个空格分开的正整数 hi,表示每棵竹子的高度。
输出格式 一个整数表示答案。
数据范围 对于 20% 的数据,保证 1≤n≤1000,1≤hi≤106。 对于 100% 的数据,保证
1≤n≤2×105,1≤hi≤1018。
输入样例:
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 步完成。
题目分析:
这里最重要要能想到的就是每一颗竹子最多会被砍6次就会变成1了,例如最大的数:
1e18-> 1e9 -> 1e4 -> 1e2 -> 1e1 -> 1
我们可以先算出来每颗数要砍多少刀,且每一刀之后的长度是多少,我们可以开一个二维数组 f[N][10]
反向给他存下来。第一维存第i个竹子,第二维存第i个竹子的最后第几刀之后的长度是多少。
如 7 ,那么 第一刀之后会变成 2, 第二刀之后边成1, 那么 f[0][0] = 2, f[o][1] = 7
,
我们可以发现,当 相同刀数之后的两颗相邻的竹子的长度相等的话,那么接下来他们一定是一起砍的,所以我们只需要遍历的相邻的竹子相同刀数且相同长度的时候,这 len 个竹子的 相同刀数以下都是一起砍的,之上就是各砍各的。所以就变成了线性砍
- 补充: 虽然之上是各砍各的,但还是可以加一个优化的,就是当前这个需要砍 k 刀,那么下一个如果高度比我这个矮,那么下一个就不需要砍,因为我砍这一课的时候已经把下一颗的给砍了。(这些所有情况的前提条件是他们的 f[i][k]的值是相等的才行)
算法1:
枚举 – 时间复杂度O(6*n):
代码:
/*
* @Author: suhuamo
* @Date: 2022-04-16 10:03:27
* @LastEditTime: 2022-04-16 11:18:07
* @FilePath: \algorithm\蓝桥杯\第十三届蓝桥杯省赛B\J.cpp
* @slogan: 也许散落在浩瀚宇宙的小行星们也知道
* 知识点: 简单模拟
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int a[N];
LL f[N][10];
LL solve(LL x)
{
return sqrt(x / 2 + 1);
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++)
{
LL x;
scanf("%lld", &x);
stack<LL> S;
while(x != 1)
{
S.push(x);
x = solve(x);
}
int idx = 1;
while(!S.empty())
{
x = S.top();
S.pop();
f[i][idx++] = x;
}
}
int res = 0;
for(int i = 0; i < n; i++)
{
if(f[i][1] == 0) continue;
int j = i + 1, idx = 1;
// 计算可以一刀砍的区间长度
while(j < n && f[i][idx] == f[j][idx]) j++;
// 计算有多少刀相同
while(1)
{
bool flag = true;
for(int k = i; k < j; k++)
{
if(f[k][idx + 1] == 0 || f[i][idx + 1] != f[k][idx + 1])
{
flag = false;
break;
}
}
if(flag) idx++;
else break;
}
// 相同的长度只需要砍 idx 刀就可以了
res += idx;
// cout << "i:" << i << ", res:" << res << endl;
// cout << res << endl;
// 再遍历每一个竹子自己独特需要砍的几次
for(int k = i; k < j; k++)
{
// 它的下一刀的值
int v = idx + 1;
// 判断最后一刀在哪里
while(f[k][v] != 0) v++;
// --才是最后一刀
v--;
res += v - idx;
}
i = j - 1;
}
cout << res << endl;
return 0;
}