标题:[贪心+数学/数学+位运算] 两种方法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;
}
完~
未经作者同意禁止转载