强行刷段位第十三天

东西终于写出来了,

唯有一句“痛快”可以表达做出来的感受。

 

做一半一直堵着是真难受啊。。。

这两天刷的题不是天梯,是Kevin突然提的问题,所以今天写的格式也稍稍不同啦。

 

开始是Kevin说要给我出一个题目:给你一个数n,让你求2^n的位数。

我:不给数据范围都是耍流氓。

Kevin:……n<10000

 

开始的时候想着,假如2^(n-1)小于5000……00(k个0),那么2^n的位数就和2^(n-1)相同,否则就+1。

然后向子问题递归。

类似于分而治之的思想?也可能是我刚做完动态规划,自然而然就顺到这个节奏上了。

 

后来在Kevin的引导下,知道了思路原来是:

如果我找到一个数a,10^a<=2^n<10^(a+1)

那么求出这个a,再+1就是答案。

 

于是乎精简出来就只有一行代码:

    a=n*log(2)/log(10)+1;

 

另外c++中无ln,log表示以e为底。语法形式为:log(10),表示以e为底10的对数。

 

这道题回味一下,你会有一种“还可以这样??”的感受。

Kevin说这个题是信息学竞赛的一个题目,也是今年清华软院夏令营的三道题中的一道题。

Kevin还说:

我让你做这道题就是想告诉你要抓住问题的本质,转换成数学或者基本算法去求解。

虽然我想不到。

 

感觉确实是不容易想到。。。

 

我以为这就结束了,没想到Kevin还有第二问在等着我。。。

就是要求2^n最后500位

 

最后整理一下问题,就变成了一道黄金段位的题目:麦森数

 

在解决麦森数之前,我们先来解决一个知识点,就是快速幂。


快速幂:

题目:

输入3个数a,b,c,求a^b mod c=?

输入描述:

三个数a,b,c

输出描述:

一个数,即a^b mod c 的答案。

样例输入:

5 10 9

样例输出:

4

数据范围及提示:

0<a,b,c<10000000000000000

 

我的答案:

其实快速幂还是挺简单的。

简单说就是:

假设a^b,b=11111

那么ans=a^(1)*a^(2)*a^(4)*a^(8)这样

然后呢a^2=(a^1)^2,a^4=(a^2)^2...

那么假设tmp=a,ans=1开始,则只需要计算五次,每次都是tmp=tmp^2,ans*=tmp

当b=10001的时候,就是存在0的时候,我计算到那一位就可以让ans不乘tmp,ans还是等于ans。

 

说白了就三句话:

1.temp不断平方

2.用b的二进制判断用不用乘

3.在ans上不断*temp

 

标准格式如下,实在不行背下来也不麻烦

int pow(int a,int b)  //a^b
{
    int res=1,arr=a;
    while(b!=0)
	{
        if(b%2) res*=arr;
        arr*=arr;
        b/=2;
    }
    return res;
}

 

而快速取模就更好理解了,就是随便你什么时候取模都不会影响结果。

那么千万别攒在一起取模,每做一轮乘法就取模。

代码:

#include <iostream>
#define ll long long
using namespace std;

ll pow(ll a,ll b,ll c)
{
	ll res=1;
	a%=c;
	while(b!=0)
	{
		if(b%2) res*=a;
		a*=a;
		b/=2;
		a%=c;
		res%=c;
	}
	return res;
}

int main()
{
	ll a=0,b=0,c=0;
	ll result;
	cin>>a>>b>>c;
	result=pow(a,b,c);
	cout<<result;
	
	return 0;
 } 

这道题比较尴尬的是,假如你所有样例都通过了,那么恭喜你,你做错了。

有一个测试样例是有问题的。

这组样例是酱婶儿的:

  • 9946531947 6644974612 999999999
  • 正确的输出应该是:
  • 83371113
  • 然而,给到的结果却是:
  • 976737430

问题就出在:
网站管理员也没有意识到

64位机下,c语言内置的long long为64位,

而无符号的long long上限为2的64次方-1=18446744073709551615

当不进行任何处理就进入计算时,在第一步中

a*a=9946531947*9946531947=98933497772691610809 > 18446744073709551615

a的平方超出了这串电话号码一样的上限,

内存中存放的是补码,越界后应该为下限+超过的部分(我理解是这样)。从而最后产生了错误答案。

 

解决方法很简单,上来就让a对c取模,这样就算后续平方也没有问题。

 

我终于如愿以偿的拿到了光荣的90分。。。

 


解决了这一题

就要来看大boss麦森数了


麦森数:

题目:

形如2^P-1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2^P-1不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。

任务:从文件中输入P(1000<P<3100000),计算2^P-1的位数和最后500位数字(用十进制高精度数表示)

输入描述:

文件中只包含一个整数P(1000<P<3100000)

输出描述:

第一行:十进制高精度数2^P-1的位数。

第2-11行:十进制高精度数2^P-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0)

不必验证2^P-1与P是否为素数。

样例输入:

1279

样例输出:

386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087

我的答案:

这题第一问我们在上面已经解决了,2^p与2^p-1位数应该是一样的

第二问首先涉及到500位,要做到的必然就是高精度。

于是首先出现了我的第一版代码:

#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
int arr[510];

void multip(int arr[],int p) //乘1次2 
{
    int i=0,j=0;
    for(i=0;i<p;i++)
    {
    	for(j=0;j<500;j++)
    	{
    		arr[j]=arr[j]*2;
		}
				
		for(j=0;j<500;j++)
		{
			arr[j+1]=arr[j+1]+arr[j]/10;
			arr[j]=arr[j]%10;
		}
	}
	
	arr[0]--;
    j=0;
    while(arr[j]<0)
    {
        arr[j]=arr[j]+10;
        j++;
        arr[j]--;
	}
	
	for(i=499;i>=0;i--)
	{
		cout<<arr[i];
		if(i%50==0)  cout<<endl;
	}
 } 

int main()
{
	int p=0,a=0;
	int len=0;
	int i=0;
	cin>>p;
	
	a=p*log(2)/log(10)+1;
    cout<<a<<endl;
    
    arr[0]=1;
    multip(arr,p);
		
	return 0;   
}

我称之为:“疯狂取幂版”。

总之就是疯狂乘2,然后存进高精度数组里。

结局就是就只通过了一部分样例,因为时间复杂度太太太高了。。。

 

然后我就开始了我的曲线救国路线。

在Kevin的指导下,先学习了快速幂,然后思考了一下快速幂和高精度怎么结合,然后放弃了。

 

emmm,我觉得我目前的弱鸡水平,还是解决不了这么高难度的问题。

不做了。。。

不行,我好蓝瘦。。。

做也不是,不做也不是。。。

啊。。。好难受。。。

 

然后我就躺在床上难受的滚来滚去。。。

恨Kevin。。。

因为就是他给我出的这道题,让我陷入了两难的境地。

于是我把x战警看完了就睡觉了。。。

 

今天一早睡了个懒觉就来改代码。

平心静气带着样例,一点一点的按程序手动走了一下。

果然找到了我的智障bug,然后把有问题的部分重新敲了敲。

就出现了接下来的:“终于AC版”

 

然后就,AC了!?!?!?

 

激顿!

 

要注意的小细节:

1.开始的时候怎么就没想到把高精度数组作为快速幂的参数。。。

以后就把高精度数组当成正常的数吧,只不过需要重新定义加减乘除规则,其实和int型,float型都是一样的。

2.要注意高精度的乘法中,承接结果的数组要初始为0,因为高精度乘法不是覆盖,而是在其上进行加法

3.高精度乘法中承接结果的数组开大一点!因为运算过程中会出现i+j啊,至少要是乘数位数的二倍!

4.注意注意注意,输出格式!

 

下面就是我的 “终于AC版” 啦,啦啦啦。

#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
int arr[510];
int res[510];
int temparr[1000];//这两个开大一点,因为下面会出现i+j=1000的情况 
int tempres[1000];

void multip(int a[],int b[],int c[]) //高精度乘法 
{
	int i=0,j=0,k=0;
	for(i=0;i<500;i++)
	{
		for(j=0;j<500;j++)
		{
			c[i+j]=c[i+j]+a[i]*b[j];
		}
	}
	
	for(k=0;k<500;k++)
	{
		c[k+1]=c[k+1]+c[k]/10;
		c[k]=c[k]%10;
	}
 } 

void pow(int arr[],int res[],int p)  //快速幂(参数为高精度) 
{
	int i=0;
    while(p!=0)
	{
        if(p%2) 
		{
			multip(res,arr,tempres);
			for(i=0;i<500;i++)
			{
				res[i]=tempres[i];
				tempres[i]=0;
			}
		}		
		multip(arr,arr,temparr);
		for(i=0;i<500;i++)
		{
			arr[i]=temparr[i];
			temparr[i]=0;
		}		
        p/=2;
    }
}

int main()
{
	int p=0,a=0;
	int len=0;
	int i=0,j=0;
	int flag=0;
	cin>>p;
	
	a=p*log(2)/log(10)+1; //输出位数 
    cout<<a<<endl;
    
    arr[0]=2;
	res[0]=1;
	
    pow(arr,res,p);

    res[0]--; //减1 (可能有借位) 
    j=0;
    while(res[j]<0)  
    {
    	res[j]=res[j]+10;
       	j++;
        res[j]--;
	}
	
	for(i=499;i>=0;i--) //输出,注意输出格式 
	{
		cout<<res[i];
		if(i%50==0)  cout<<endl;
	}

	return 0;
    
}

 

今天真的很荣幸可以AC掉Mason这道题目。

感谢Kevin,感谢codevs,感谢Devc++,感谢每一位支持我的字符,感谢在座的库函数和测试样例。

是你们的帮助和鼓励让我完成了这个难题。

我们下次再见~

(鞠躬)

 

(台下:掌声+撒花)

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值