通过减一或除以一个数将n变为1------贪心算法

本文介绍了一个具体的贪心算法案例,通过解决特定问题的过程,详细分析了初始代码的问题所在,并逐步改进算法实现,最终解决了时间限制超时的问题。

初涉贪心算法,先将百度百科的定义贴下,留作慢慢体会:
贪心算法*(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解*。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
题目描述:
这里写图片描述
我原本的代码:

#include<stdio.h>
int main()
{
    long long a, b, n, k,x, cnt = 0;
    double t1, t2;
    scanf("%lld%lld%lld%lld", &n,&k,&a,&b);
    x = n;
    while (x != 1)
    {
        t1 = t2 = -1.0;
        t1 = 1.0/a;
        if (x%k == 0) t2 = 1.0*(x - x * 1.0 / k) /b ;
        if (t1 > t2) {
            x -= 1;
            cnt += a;
            continue;
        }
        x /= k;cnt += b;
    }
    printf("%lld", cnt);
    return 0;
}

分析:
最开始我的想法:按照性价比(也就是动态的用长度除以开销来计算)的思想,分两种情况:
(1)不能被k整除时,循环减一到整除,记录开销。
(2)可以被k整除时,计算减一和除以k的分别性价比,选优者记录开销。
这样做的结果是:测试数据没有太大问题,但是提交却总是Time Limit
分析下来原因有如下:
(1)未考虑特殊情况,即当 倍数k=1 的时候,只要是进行除法,n的值永远不变,这样不可能跳出循环。
(2)傻傻的用循环减一的方法来使n变得可以被k整除,进而讨论。这样反复循环,效率自然低得多。

改进的措施:
(1)考虑到 k==1 的情况,如果k==1,则此时只能用减一的方法来使n变为1
(2)对于循环减一直到整除的做法,改为一次性减到可以被整除
(3)可以被整除后,有两种策略 :a.直接除以k,得到 n/k ,减少的数目相当于 (n-n/k),开销为b; b.一直减一直到与(n-n/k)这个减少的数目相同为止,计算开销为 (n-n/k)*a通过比较前两种策略的优劣,可以加到cnt来得到局部最优解。
此时还存在一个问题,如果算到 n小于k时,循环开始时的减法会一直减到0(因为零除以任何非零数都得零),而我们期望得到的n为1,因此,这种情况需要另加判断,一旦算到这一步,就直接跳出循环,计算减到1所需的值加上即可。

改后代码:

#include<stdio.h>
int main()
{
    long long n, k, a, b;
    long long p, t1,t2,cnt = 0;
    scanf("%lld%lld%lld%lld", &n, &k, &a, &b);
    if (k == 1) {
        printf("%lld", (n - 1)*a);
        return 0;
    }
    while (n != 1)
    {
        if (n%k != 0) {     //直到可以整除再比较
            cnt += n%k *a;
            n-=n%k; //减到可以整除为止
        }   
        //如果减到可以整除时n仍大于1,就接着向下执行
        if (n > 1 && n%k == 0) {
            t1 = b; //直接除到的花销
            t2 = (n - n / k)*a; //一直减到刚才消去位置的花销
            if (t1 < t2) {
                cnt += t1;
            }
            else {
                cnt += t2;
            }
            n /= k;
        }
        if (n < k) break;
    }
    cnt += (n - 1)*a;
    printf("%lld", cnt);
    return 0;
}
### 华为OD机试分糖果问题的Python解题思路 #### 问题描述 小明从糖果盒中随意抓取量的糖果,每次可以执行三种操作之:取出半糖果并舍去余;如果当前糖果目为奇,则可以从盒子中再拿一个糖果将手里的一个糖果放回去。目标是最少经过几次这样的操作能够使手中的糖果变为颗。 为了达到这个目的,采用贪心策略来解决问题[^3]。具体来说,在每步都尽可能快速地少糖果的量直到只剩下颗为止。当遇到奇个糖果时,优先考虑加而不是,因为这样可以在下轮更有效地通过除以2的方式大量削糖果总。 #### 贪婪算法分析 对于任意正整n表示初始糖果- 如果 n 是偶,则直接将其除以2; - 若 n 为奇,则判断 (n+1)/2 和 (n−1)/2 的大小关系,选择较小的那个作为下步的结果,这是因为我们希望更快接近1。 这种做法基于这样一个事实——当我们面对两个连续的奇值时,较大的那个总是可以通过增加1变成一个小得多的新值(即其半),而较小的一个则需要两次操作才能完成同样的效果(先后除)[^4]。 #### Python代码实现 下面给出了解决上述问题的具体Python程序: ```python def min_steps_to_one(n): steps = 0 while n != 1: if n % 2 == 0: n //= 2 elif ((n + 1) // 2) < ((n - 1) // 2): n += 1 else: n -= 1 steps += 1 return steps if __name__ == "__main__": candy_count = int(input().strip()) print(min_steps_to_one(candy_count)) ``` 此函接收一个`n`代表起始的糖果量,并返回将这些糖果少到只有一个所需的最小步。主程序部分读入用户输入的据并调用该方法打印最终结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值