[贪心+数学/数学+位运算] 两种方法O(1)解决 消减整数

 标题:[贪心+数学/数学+位运算] 两种方法O(1)解决 消减整数 

个人主页@水墨不写bug

目录

 一、题目:消减整数(Newcoder)

 二、题目分析

1.理解题意:

 2.解决问题

解法详解一:贪心+数学

解法一参考代码:

解法详解二:数学+位运算

解法二参考代码:


 正文开始:

前言:

        本文是我在刷题的时候偶然遇到的算法题,对此题我有自己想出来的做法,可以达到O(1)时间复杂度,与常用的解法相比 方法比较巧妙,于是分享出来供参考学习,如果有错误,也欢迎不吝赐教。

 一、题目:消减整数(Newcoder)

题号:NC219038
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld


题目描述

        给出一个正整数H,从1开始减,第一次必须减1,每次减的数字都必须和上一次相同或者是上一次的两倍,请问最少需要几次能把H恰好减到0。


输入描述:

        第一行给出一个正整数T,  T >= 1 并且 T <= 10^4

        接下来T行每行一个整数H,  满足  1≤H≤10^9


输出描述:

每行一个正整数代表最少的次数

示例1

输入

3(T)
接下来T行输入测试用例
3
5
7

输出

2
3
3

 二、题目分析

1.理解题意:

        为了方便,我们只举一个例子:3

        3第一次-1;得到2

        第二次有两种选择:继续-1 或者 (减1的二倍)-2

        如果选择-2,得到0,则最短路径就是2。

 

 2.解决问题

        在正式讲解可行的算法之前,先考虑暴力解法(因为可行解法往往是暴力解法的优化):

        对于一个数,每次要么-A,要么-2*A;(A是下一次需要减的数)

                于是暴力解法就是对每一次减,都逝一逝(A和2*A)。这里我就不试了,为什么想必大家都知道。 

        复杂度:O(2^N)

解法详解一:贪心+数学

         贪心:每次都减2*A,这样能减的更快,用的次数更少;但是不能保证正确性,比如下面的这个特例:

        这就由于贪心而错失了正确答案。 正确的处理方法是在每次贪心之前,需要进行一步判断。

        我们暂且称上述的贪心为短视的“简单贪心”。假设一个数,经过“简单贪心”刚好可以减到0:

这就意味着这个数一定 是2a的整数倍!!

        那么就只需要在每次减之前,让这个数 % (2*A),如果等于0,这就意味着我们目前可以得正确的结果。意味着我们走在正确的二叉树分支上。意味着我们是在得到正确答案的基础上贪心!


解法一参考代码:

#include <iostream>
using namespace std;

int t, h;

int fun()
{
    int ret = 0, a = 1;

    while(h)
    {
        h -= a;
        ret++;
        if(h % (a * 2) == 0)
        {
            a *= 2;
        }
    }
    return ret;
}

int main()
{
    cin >> t;
    while(t--)
    {
        cin >> h;
        cout << fun() << endl;
    }
    return 0;
}

解法详解二:数学+位运算

         这个解法是我自己想到的解法。起初我想到位运算,因为每次减的数都是2的N次方,

(1,2,4,8,16....),于是我写出了下面的代码,并暗自得意这道题,也就不过如此嘛:

int cut_int(int n)
{
	int ans = 0;
	
	for(int i = 0;i < 32;++i)
	{
		if(((n >> i) & 1) == 1)
		{
			ans++;
		}
	}
	return ans;
}
int main()
{
	int n;
	cin>>n;
	int tem;
	while(n--)
	{
		cin>>tem;
		cout<<cut_int(tem)<<endl;
	}
	return 0;
}

        但是提交后,我立刻发现了问题所在:2的倍数是一个次数一个次数加上去的,在合理的范围内,每一个2的次方数都至少要出现一次! 

        仅仅统计二进制1位的出现次数得出的是错误的答案!因为减的数字是从1开始的。不可能直接越过4而减去8/16/32.....

        于是, 我们可以先预先算出2出现的最高次数N,再一次减去(2的次方项 1,2,4,8,16....),这样不就保证不会出现上述的越级的行为!!

        对于2的次方项,我们可以通过等比数列前n项求和公式计算:

        然后让目标数字num-(Sn),得到的剩下的数,不就直接可以通过统计二进制位的方式来计算减的次数了嘛!!

总结记目标数A,2出现的最高次项N

        由于减去的数字从1开始,以次乘二,所以减去的数中一定是依次增大的(1,2,4,8.)2的次方项一定会每个都至少出现一次!

        我们通过一次操作:让目标数-(2的次方项1,2,4...直到最高次项N,这个过程等比数列求和计算),同时操作次数+N;从此我们就得到了新的目标数B,B就可以通过计算二进制位上1的个数来得到最终的答案。 


解法二参考代码:

#include <iostream>
#include<cmath>
using namespace std;

int cut_int(int n)
{
	int ans = 0;
	
	int fn = 0;
	for(int i = 1; pow(2,i) - 1 <= n;++i)
	{
		fn = i;
	}
	
	ans += fn;
	
	n -= (pow(2,fn)-1);
	
	for(int i = 0;i < 32;++i)
	{
		if(((n >> i) & 1) == 1)
		{
			ans++;
		}
	}
	return ans;
}
int main()
{
	int n;
	cin>>n;
	int tem;
	while(n--)
	{
		cin>>tem;
		cout<<cut_int(tem)<<endl;
	}
	
	return 0;
}

完~

未经作者同意禁止转载

好的,我来给你讲一下解决这个问题的思路。首先,我们需要了解什么是埃及分数。 埃及分数,又称古埃及分数,是指所有分子为1的不同分母的有限个倒数之和。例如,4/13可以表示为1/4+1/18+1/468,即4/13=(1/4)+(1/18)+(1/468)。 那么,如何将一个真分数表示为埃及分数之和呢?这里我们可以采用贪心算法解决。具体来说,我们从大到小枚举所有可能的分母,每次选取最大的那个分母,然后将其作为一个分数加入到结果中,然后将原来的真分数减去该分数,继续进行上述操作,直至分子为1。 下面是 Python 代码实现: ```python def egypt_fraction(num, den): result = [] while num != 1: # 找到最大的分母 x = int(den / num) + 1 result.append((1, x)) num = num * x - den den *= x result.append((1, den)) return result num = int(input("请输入分子:")) den = int(input("请输入分母:")) result = egypt_fraction(num, den) print(f"{num}/{den}=", end="") for i in range(len(result) - 1): print(f"{result[i][0]}/{result[i][1]}+", end="") print(f"{result[-1][0]}/{result[-1][1]}") ``` 其中,`(1, x)` 表示一个分数,分子为1,分母为x。程序输出的结果为: ``` 请输入分子:7 请输入分母:8 7/8=1/2+1/3+1/24 ``` 如果输入分子为15,分母为16,则程序输出的结果为: ``` 请输入分子:15 请输入分母:16 15/16=1/2+1/3+1/10+1/240 ``` 希望这个解答能够帮助到你!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水墨不写bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值