试题 J: 砍竹子

题目链接:

点击跳转

题目描述:

这天,小明在砍竹子,他面前有 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;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值